mirror of
https://github.com/wassname/pytorch-lightning.git
synced 2026-09-12 12:40:20 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e41bf0a047 | ||
|
|
cd594a1d1a | ||
|
|
d923acd606 | ||
|
|
b35229d9ab | ||
|
|
978519fc33 | ||
|
|
aab4fe860d | ||
|
|
a7f3974f95 | ||
|
|
7ce5b3f62f | ||
|
|
f95eca3ea5 | ||
|
|
f9a3e03e1c | ||
|
|
9fa2806605 | ||
|
|
3e38005a61 | ||
|
|
8fbaccddae | ||
|
|
32dd803b1e | ||
|
|
5a9afb11cc | ||
|
|
9a5307dc30 | ||
|
|
37729f0a17 | ||
|
|
446a1b5d45 | ||
|
|
4e9fd95f79 | ||
|
|
1865de1ff8 | ||
|
|
661a1c6fe6 | ||
|
|
248495b1d1 | ||
|
|
f6b8b175bb | ||
|
|
f79bdf2327 | ||
|
|
9f8ab7c29e | ||
|
|
8347a6c87e | ||
|
|
4df4d4cc03 | ||
|
|
37647d835a | ||
|
|
b86d223889 | ||
|
|
d5ca464cc6 | ||
|
|
a4b43ce095 | ||
|
|
28c3bcb0c0 | ||
|
|
48eabf0751 | ||
|
|
7fb868bfd8 | ||
|
|
5db90e32eb | ||
|
|
c6244594a6 | ||
|
|
d955baa235 | ||
|
|
b47b881f78 | ||
|
|
56fa2075a5 | ||
|
|
5afae59715 | ||
|
|
2aba70e228 | ||
|
|
05cea3ff8b | ||
|
|
0db422777c | ||
|
|
e7c12d936e | ||
|
|
2924ebeda5 | ||
|
|
2b82fe0fb7 | ||
|
|
42c892b3fc | ||
|
|
061101b448 | ||
|
|
0de181b8d4 | ||
|
|
0e4aab7f0e | ||
|
|
f18aee30a5 | ||
|
|
e6e325c853 | ||
|
|
7225e5d6d5 | ||
|
|
ad3c6acca3 | ||
|
|
792ad00ff9 | ||
|
|
ab6794406e | ||
|
|
1424157731 | ||
|
|
0f77603ead | ||
|
|
4103a5ca73 | ||
|
|
67f6e7bb19 | ||
|
|
58d52c25a1 | ||
|
|
b0281395bf | ||
|
|
c1bbc2158f | ||
|
|
6111edaf82 | ||
|
|
e1b45ca492 | ||
|
|
e611223dc8 | ||
|
|
699bd2cb50 | ||
|
|
4a38231a03 | ||
|
|
e04dfb37fd | ||
|
|
57274b8331 | ||
|
|
5cfff1e5c1 | ||
|
|
416187fd56 | ||
|
|
3dfcef6994 | ||
|
|
0fac2d64cf | ||
|
|
82223275ba | ||
|
|
a7f26a67ac | ||
|
|
d30a3bf17a | ||
|
|
c6dde49296 | ||
|
|
d29a693590 | ||
|
|
65a2cf6104 | ||
|
|
d8920169ac | ||
|
|
5ca6b40308 | ||
|
|
e5050700ce | ||
|
|
ec87c0efe1 | ||
|
|
188b598f29 | ||
|
|
4c7c933326 | ||
|
|
f14700a16a | ||
|
|
5395383910 | ||
|
|
2044126821 | ||
|
|
e2cabb03ba | ||
|
|
b8666bf354 | ||
|
|
19c2b8fc9e | ||
|
|
792ba59b78 | ||
|
|
93222cf86d | ||
|
|
426bb19846 | ||
|
|
f136cc946a | ||
|
|
84aec24acc | ||
|
|
a94e9d8e12 | ||
|
|
46322b906b | ||
|
|
96c2a2de50 | ||
|
|
0eab1e42b2 | ||
|
|
453568179b | ||
|
|
d95e693598 | ||
|
|
6e0a562ecb | ||
|
|
5f1f3f6acc | ||
|
|
ec10119e97 | ||
|
|
608a90a490 | ||
|
|
8088052825 | ||
|
|
49e04de5ac | ||
|
|
dcaba55251 | ||
|
|
6e3e740a7f | ||
|
|
ff2a21a08a | ||
|
|
1cf2e228ba | ||
|
|
c0bd203cff | ||
|
|
fbc1272796 | ||
|
|
46b55d9aaa | ||
|
|
c0b0c91d24 | ||
|
|
ac6d0154c2 | ||
|
|
b12eb8d73a | ||
|
|
491100abdd |
@@ -0,0 +1,59 @@
|
|||||||
|
# How to become a core contributor
|
||||||
|
|
||||||
|
Thanks for your interest in joining the Lightning team! We’re a rapidly growing project which is poised to become the go-to framework for DL researchers!
|
||||||
|
We're currently recruiting for a team of 5 core maintainers.
|
||||||
|
|
||||||
|
As a core maintainer you will have a strong say in the direction of the project. Big changes will require a majority of maintainers to agree.
|
||||||
|
|
||||||
|
### Code of conduct
|
||||||
|
First and foremost, you'll be evaluated against [these core values](https://github.com/williamFalcon/pytorch-lightning/blob/master/.github/CONTRIBUTING.md). Any code we commit or feature we add needs to align with those core values.
|
||||||
|
|
||||||
|
### The bar for joining the team
|
||||||
|
Lightning is being used to solve really hard problems at the top AI labs in the world. As such, the bar for adding team members is extremely high. Candidates must have solid engineering skills, have a good eye for user experience, and must be a power user of Lightning and PyTorch.
|
||||||
|
|
||||||
|
With that said, the Lightning team will be diverse and a reflection of an inclusive AI community. You don't have to be an engineer to conntribute! Scientists with great usability intuition and PyTorch ninja skills are welcomed!
|
||||||
|
|
||||||
|
### Responsibilities:
|
||||||
|
The responsibilities mainly revolve around 3 things.
|
||||||
|
|
||||||
|
#### Github issues
|
||||||
|
- Here we want to help users have an amazing experience. These range from questions from new people getting into DL to questions from researchers about doing something esoteric with Lightning
|
||||||
|
Often, these issues require some sort of bug fix, document clarification or new functionality to be scoped out.
|
||||||
|
|
||||||
|
- To become a core member you must resolve at least 10 Github issues which align with the API design goals for Lightning. By the end of these 10 issues I should feel comfortable in the way you answer user questions
|
||||||
|
Pleasant/helpful tone.
|
||||||
|
|
||||||
|
- Can abstract from that issue or bug into functionality that might solve other related issues or makes the platform more flexible.
|
||||||
|
|
||||||
|
- Don’t make users feel like they don’t know what they’re doing. We’re here to help and to make everyone’s experience delightful.
|
||||||
|
|
||||||
|
#### Pull requests
|
||||||
|
|
||||||
|
- Here we need to ensure the code that enters Lightning is high quality. For each PR we need to:
|
||||||
|
- Make sure code coverage does not decrease
|
||||||
|
- Documents are updated
|
||||||
|
- Code is elegant and simple
|
||||||
|
- Code is NOT overly engineered or hard to read
|
||||||
|
- Ask yourself, could a non-engineer understand what’s happening here?
|
||||||
|
- Make sure new tests are written
|
||||||
|
- Is this NECESSARY for Lightning? There are some PRs which are just purely about adding engineering complexity which have no place in Lightning.
|
||||||
|
Guidance
|
||||||
|
- Some other PRs are for people who are wanting to get involved and add something unnecessary. We do want their help though! So don’t approve the PR, but direct them to a Github issue that they might be interested in helping with instead!
|
||||||
|
- To be considered for core contributor, please review 10 PRs and help the authors land it on master. Once you've finished the review, ping me
|
||||||
|
for a sanity check. At the end of 10 PRs if your PR reviews are inline with expectations described above, then you can merge PRs on your own going forward,
|
||||||
|
otherwise we'll do a few more until we're both comfortable :)
|
||||||
|
|
||||||
|
#### Project directions
|
||||||
|
There are some big decisions which the project must make. For these I expect core contributors to have something meaningful to add if it’s their area of expertise.
|
||||||
|
|
||||||
|
#### Diversity
|
||||||
|
Lightning should reflect the broader community it serves. As such we should have scientists/researchers from
|
||||||
|
different fields contributing!
|
||||||
|
|
||||||
|
The first 5 core contributors will fit this profile. Thus if you overlap strongly with experiences and expertise as someone else on the team, you might have to wait until the next set of contributors are added.
|
||||||
|
|
||||||
|
#### Summary: Requirements to apply
|
||||||
|
- Solve 10 Github issues. The goal is to be inline with expectations for solving issues by the last one so you can do them on your own. If not, I might ask you to solve a few more specific ones.
|
||||||
|
- Do 10 PR reviews. The goal is to be inline with expectations for solving issues by the last one so you can do them on your own. If not, I might ask you to solve a few more specific ones.
|
||||||
|
|
||||||
|
If you want to be considered, ping me on gitter and start [tracking your progress here](https://docs.google.com/spreadsheets/d/15D58gp8DvI0Z6qbbYVRuaWioiwzafcP58-UlbuO_CMU/edit?usp=sharing).
|
||||||
+18
-7
@@ -1,15 +1,14 @@
|
|||||||
# Contributing
|
# Contributing
|
||||||
Welcome to the PyTorch Lightning community! We're building the most advanced research platform on the planet to implement the latest, best practices that the amazing PyTorch team rolls out!
|
Welcome to the PyTorch Lightning community! We're building the most advanced research platform on the planet to implement the latest, best practices that the amazing PyTorch team rolls out!
|
||||||
|
|
||||||
## One less thing to remember
|
## Main Core Value: One less thing to remember
|
||||||
Simplify the API as much as possible from the user perspective. Any additions or improvements should minimize things the user needs to remember.
|
Simplify the API as much as possible from the user perspective. Any additions or improvements should minimize things the user needs to remember.
|
||||||
|
|
||||||
For example: One benefit of the validation_step is that the user doesn't have to remember to set the model to .eval(). This avoids all sorts of subtle errors the user could make.
|
For example: One benefit of the validation_step is that the user doesn't have to remember to set the model to .eval(). This avoids all sorts of subtle errors the user could make.
|
||||||
|
|
||||||
## Lightning Design Principles
|
## Lightning Design Principles
|
||||||
We encourage all sorts of contributions you're interested in adding! When coding for lightning, please follow these principles.
|
We encourage all sorts of contributions you're interested in adding! When coding for lightning, please follow these principles.
|
||||||
|
#### No PyTorch Interference
|
||||||
#### No PyTorch interference
|
|
||||||
We don't want to add any abstractions on top of pure PyTorch. This gives researchers all the control they need without having to learn yet another framework.
|
We don't want to add any abstractions on top of pure PyTorch. This gives researchers all the control they need without having to learn yet another framework.
|
||||||
|
|
||||||
#### Simple Internal Code
|
#### Simple Internal Code
|
||||||
@@ -21,17 +20,25 @@ There are 1,000 ways to do something. However, something eventually becomes stan
|
|||||||
When something becomes a best practice, we add it to the framework. This likely looks like code in utils or in the model file that everyone keeps adding over and over again across projects. When this happens, bring that code inside the trainer and add a flag for it.
|
When something becomes a best practice, we add it to the framework. This likely looks like code in utils or in the model file that everyone keeps adding over and over again across projects. When this happens, bring that code inside the trainer and add a flag for it.
|
||||||
|
|
||||||
#### Simple External API
|
#### Simple External API
|
||||||
What makes sense to you may not make sense to others. Create an issue with an API change suggestion and validate that it makes sense for others. Treat code changes how you treat a startup: validate that it's a needed feature, then add if it makes sense for many people.
|
What makes sense to you may not make sense to others. Create an issue with an API change suggestion and validate that it makes sense for others. Treat code changes how you treat a startup: validate that it's a needed feature, then add if it makes sense for many people.
|
||||||
|
|
||||||
|
#### Backward-compatible API
|
||||||
|
We all hate updating our deep learning packages because we don't want to refactor a bunch of stuff. In Lightning, we make sure every change we make which could break an API is backwards compatible with good deprecation warnings.
|
||||||
|
|
||||||
|
You shouldn't be afraid to upgrade Lightning :)
|
||||||
|
|
||||||
#### Gain User Trust
|
#### Gain User Trust
|
||||||
As a researcher you can't have any part of your code going wrong. So, make thorough tests that ensure an implementation of a new trick or subbtle change is correct.
|
As a researcher you can't have any part of your code going wrong. So, make thorough tests that ensure an implementation of a new trick or subbtle change is correct.
|
||||||
|
|
||||||
## Contribution types
|
#### Interoperability
|
||||||
|
Have a favorite feature from other libraries like fast.ai or transformers? Those should just work with lightning as well. Grab your favorite model or learning rate scheduler from your favorite library and run it in Lightning.
|
||||||
|
|
||||||
|
## Contribution Types
|
||||||
Currently looking for help implementing new features or adding bug fixes.
|
Currently looking for help implementing new features or adding bug fixes.
|
||||||
|
|
||||||
A lot of good work has already been done in project mechanics (requirements.txt, setup.py, pep8, badges, ci, etc...) we're in a good state there thanks to all the early contributors (even pre-beta release)!
|
A lot of good work has already been done in project mechanics (requirements.txt, setup.py, pep8, badges, ci, etc...) we're in a good state there thanks to all the early contributors (even pre-beta release)!
|
||||||
|
|
||||||
## Bug fixes:
|
## Bug Fixes:
|
||||||
1. Submit a github issue.
|
1. Submit a github issue.
|
||||||
2. Fix it.
|
2. Fix it.
|
||||||
3. Submit a PR!
|
3. Submit a PR!
|
||||||
@@ -40,3 +47,7 @@ A lot of good work has already been done in project mechanics (requirements.txt,
|
|||||||
1. Submit a github issue.
|
1. Submit a github issue.
|
||||||
2. We'll agree on the feature scope.
|
2. We'll agree on the feature scope.
|
||||||
3. Submit a PR! (with updated docs and tests 🙃).
|
3. Submit a PR! (with updated docs and tests 🙃).
|
||||||
|
|
||||||
|
## Coding Styleguide
|
||||||
|
1. Test the code with flake8.
|
||||||
|
2. Use f-strings.
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ test_tube_exp/
|
|||||||
tests/tests_tt_dir/
|
tests/tests_tt_dir/
|
||||||
tests/save_dir
|
tests/save_dir
|
||||||
default/
|
default/
|
||||||
|
lightning_logs/
|
||||||
|
tests/tests/
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|||||||
+4
-1
@@ -1,5 +1,8 @@
|
|||||||
# use this to run tests
|
# use this to run tests
|
||||||
|
rm -rf _ckpt_*
|
||||||
rm -rf tests/save_dir*
|
rm -rf tests/save_dir*
|
||||||
rm -rf tests/mlruns_*
|
rm -rf tests/mlruns_*
|
||||||
coverage run --source pytorch_lightning -m py.test pytorch_lightning tests examples -v --doctest-modules
|
rm -rf tests/tests/*
|
||||||
|
rm -rf lightning_logs
|
||||||
|
coverage run --source pytorch_lightning -m py.test pytorch_lightning tests pl_examples -v --doctest-modules
|
||||||
coverage report -m
|
coverage report -m
|
||||||
|
|||||||
+4
-5
@@ -35,7 +35,7 @@ matrix:
|
|||||||
addons:
|
addons:
|
||||||
homebrew:
|
homebrew:
|
||||||
# update: true
|
# update: true
|
||||||
packages: python3
|
packages: python3.6
|
||||||
before_install:
|
before_install:
|
||||||
- pip3 install virtualenv
|
- pip3 install virtualenv
|
||||||
- virtualenv -p python3 ~/venv
|
- virtualenv -p python3 ~/venv
|
||||||
@@ -51,14 +51,13 @@ matrix:
|
|||||||
cache: pip
|
cache: pip
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- pip install -r requirements.txt
|
- pip install future # needed for `builtins`
|
||||||
- pip install -r ./tests/requirements.txt
|
- sudo pip install tox
|
||||||
- pip --version ; pip list
|
|
||||||
|
|
||||||
script:
|
script:
|
||||||
# integration
|
# integration
|
||||||
- tox --sitepackages
|
- tox --sitepackages
|
||||||
- python setup.py install --dry-run
|
- pip install --editable .
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
- coverage report
|
- coverage report
|
||||||
|
|||||||
+3
-3
@@ -16,9 +16,9 @@ exclude *.svg
|
|||||||
recursive-include pytorch_lightning *.py
|
recursive-include pytorch_lightning *.py
|
||||||
|
|
||||||
# include examples
|
# include examples
|
||||||
recursive-include examples *.py
|
recursive-include pl_examples *.py
|
||||||
recursive-include examples *.md
|
recursive-include pl_examples *.md
|
||||||
recursive-include examples *.sh
|
recursive-include pl_examples *.sh
|
||||||
|
|
||||||
# exclude tests from package
|
# exclude tests from package
|
||||||
recursive-exclude tests *
|
recursive-exclude tests *
|
||||||
|
|||||||
@@ -10,13 +10,15 @@
|
|||||||
[](https://badge.fury.io/py/pytorch-lightning)
|
[](https://badge.fury.io/py/pytorch-lightning)
|
||||||
[](https://pepy.tech/project/pytorch-lightning)
|
[](https://pepy.tech/project/pytorch-lightning)
|
||||||
[](https://travis-ci.org/williamFalcon/pytorch-lightning)
|
[](https://travis-ci.org/williamFalcon/pytorch-lightning)
|
||||||
[](https://ci.appveyor.com/project/Borda/pytorch-lightning)
|
[](https://ci.appveyor.com/project/williamFalcon/pytorch-lightning)
|
||||||
[](https://github.com/williamFalcon/pytorch-lightning/tree/master/tests#running-coverage)
|
[](https://github.com/williamFalcon/pytorch-lightning/tree/master/tests#running-coverage)
|
||||||
[](https://www.codefactor.io/repository/github/borda/pytorch-lightning)
|
[](https://www.codefactor.io/repository/github/borda/pytorch-lightning)
|
||||||
|
|
||||||
[](https://pytorch-lightning.readthedocs.io/en/latest)
|
[](https://pytorch-lightning.readthedocs.io/en/latest)
|
||||||
[](https://gitter.im/PyTorch-Lightning/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
[](https://gitter.im/PyTorch-Lightning/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||||
[](https://github.com/williamFalcon/pytorch-lightning/blob/master/LICENSE)
|
[](https://github.com/williamFalcon/pytorch-lightning/blob/master/LICENSE)
|
||||||
|
[](https://shields.io/)
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
removed until codecov badge isn't empy. likely a config error showing nothing on master.
|
removed until codecov badge isn't empy. likely a config error showing nothing on master.
|
||||||
[](https://codecov.io/gh/Borda/pytorch-lightning)
|
[](https://codecov.io/gh/Borda/pytorch-lightning)
|
||||||
@@ -52,7 +54,7 @@ Lightning sets up all the boilerplate state-of-the-art training for you so you c
|
|||||||
- [Lightning features](https://github.com/williamFalcon/pytorch-lightning#lightning-automates-all-of-the-following-each-is-also-configurable)
|
- [Lightning features](https://github.com/williamFalcon/pytorch-lightning#lightning-automates-all-of-the-following-each-is-also-configurable)
|
||||||
- [Examples](https://github.com/williamFalcon/pytorch-lightning#examples)
|
- [Examples](https://github.com/williamFalcon/pytorch-lightning#examples)
|
||||||
- [Tutorials](https://github.com/williamFalcon/pytorch-lightning#tutorials)
|
- [Tutorials](https://github.com/williamFalcon/pytorch-lightning#tutorials)
|
||||||
- [Contributing](https://github.com/williamFalcon/pytorch-lightning/blob/master/CONTRIBUTING.md)
|
- [Contributing](https://github.com/williamFalcon/pytorch-lightning/blob/master/.github/CONTRIBUTING.md)
|
||||||
- [Bleeding edge install](https://github.com/williamFalcon/pytorch-lightning#bleeding-edge)
|
- [Bleeding edge install](https://github.com/williamFalcon/pytorch-lightning#bleeding-edge)
|
||||||
- [Lightning Design Principles](https://github.com/williamFalcon/pytorch-lightning#lightning-design-principles)
|
- [Lightning Design Principles](https://github.com/williamFalcon/pytorch-lightning#lightning-design-principles)
|
||||||
- [Asking for help](https://github.com/williamFalcon/pytorch-lightning#asking-for-help)
|
- [Asking for help](https://github.com/williamFalcon/pytorch-lightning#asking-for-help)
|
||||||
@@ -128,7 +130,7 @@ class CoolSystem(pl.LightningModule):
|
|||||||
@pl.data_loader
|
@pl.data_loader
|
||||||
def test_dataloader(self):
|
def test_dataloader(self):
|
||||||
# OPTIONAL
|
# OPTIONAL
|
||||||
return DataLoader(MNIST(os.getcwd(), train=True, download=True, transform=transforms.ToTensor()), batch_size=32)
|
return DataLoader(MNIST(os.getcwd(), train=False, download=True, transform=transforms.ToTensor()), batch_size=32)
|
||||||
```
|
```
|
||||||
2. Fit with a [trainer](https://williamfalcon.github.io/pytorch-lightning/Trainer/)
|
2. Fit with a [trainer](https://williamfalcon.github.io/pytorch-lightning/Trainer/)
|
||||||
```python
|
```python
|
||||||
@@ -150,20 +152,20 @@ Here are more advanced examples
|
|||||||
trainer = Trainer(max_nb_epochs=1, train_percent_check=0.1)
|
trainer = Trainer(max_nb_epochs=1, train_percent_check=0.1)
|
||||||
|
|
||||||
# train on 4 gpus (lightning chooses GPUs for you)
|
# train on 4 gpus (lightning chooses GPUs for you)
|
||||||
# trainer = Trainer(max_nb_epochs=1, gpus=4)
|
# trainer = Trainer(max_nb_epochs=1, gpus=4, distributed_backend='ddp')
|
||||||
|
|
||||||
# train on 4 gpus (you choose GPUs)
|
# train on 4 gpus (you choose GPUs)
|
||||||
# trainer = Trainer(max_nb_epochs=1, gpus=[0, 1, 3, 7])
|
# trainer = Trainer(max_nb_epochs=1, gpus=[0, 1, 3, 7], distributed_backend='ddp')
|
||||||
|
|
||||||
# train on 32 gpus across 4 nodes (make sure to submit appropriate SLURM job)
|
# train on 32 gpus across 4 nodes (make sure to submit appropriate SLURM job)
|
||||||
# trainer = Trainer(max_nb_epochs=1, gpus=8, nb_gpu_nodes=4)
|
# trainer = Trainer(max_nb_epochs=1, gpus=8, nb_gpu_nodes=4, distributed_backend='ddp')
|
||||||
|
|
||||||
# train (1 epoch only here for demo)
|
# train (1 epoch only here for demo)
|
||||||
trainer.fit(model)
|
trainer.fit(model)
|
||||||
|
|
||||||
# view tensorboard logs
|
# view tensorboard logs
|
||||||
print('View tensorboard logs by running\ntensorboard --logdir %s' % os.getcwd())
|
logging.info(f'View tensorboard logs by running\ntensorboard --logdir {os.getcwd()}')
|
||||||
print('and going to http://localhost:6006 on your browser')
|
logging.info('and going to http://localhost:6006 on your browser')
|
||||||
```
|
```
|
||||||
|
|
||||||
When you're all done you can even run the test set separately.
|
When you're all done you can even run the test set separately.
|
||||||
@@ -292,6 +294,7 @@ Lightning also adds a text column with all the hyperparameters for this experime
|
|||||||
|
|
||||||
#### Distributed training
|
#### Distributed training
|
||||||
|
|
||||||
|
- [Implement Your Own Distributed (DDP) training](https://williamfalcon.github.io/pytorch-lightning/Trainer/hooks/#init_ddp_connection)
|
||||||
- [16-bit mixed precision](https://williamfalcon.github.io/pytorch-lightning/Trainer/Distributed%20training/#16-bit-mixed-precision)
|
- [16-bit mixed precision](https://williamfalcon.github.io/pytorch-lightning/Trainer/Distributed%20training/#16-bit-mixed-precision)
|
||||||
- [Multi-GPU](https://williamfalcon.github.io/pytorch-lightning/Trainer/Distributed%20training/#Multi-GPU)
|
- [Multi-GPU](https://williamfalcon.github.io/pytorch-lightning/Trainer/Distributed%20training/#Multi-GPU)
|
||||||
- [Multi-node](https://williamfalcon.github.io/pytorch-lightning/Trainer/Distributed%20training/#Multi-node)
|
- [Multi-node](https://williamfalcon.github.io/pytorch-lightning/Trainer/Distributed%20training/#Multi-node)
|
||||||
@@ -335,10 +338,10 @@ Lightning also adds a text column with all the hyperparameters for this experime
|
|||||||
- [Run test set](https://williamfalcon.github.io/pytorch-lightning/Trainer/Testing%20loop/)
|
- [Run test set](https://williamfalcon.github.io/pytorch-lightning/Trainer/Testing%20loop/)
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
- [GAN](https://github.com/williamFalcon/pytorch-lightning/tree/master/examples/domain_templates/gan.py)
|
- [GAN](https://github.com/williamFalcon/pytorch-lightning/tree/master/pl_examples/domain_templates/gan.py)
|
||||||
- [MNIST](https://github.com/williamFalcon/pytorch-lightning/tree/master/examples/basic_examples)
|
- [MNIST](https://github.com/williamFalcon/pytorch-lightning/tree/master/pl_examples/basic_examples)
|
||||||
- [Other projects using Lightning](https://github.com/williamFalcon/pytorch-lightning/network/dependents?package_id=UGFja2FnZS0zNzE3NDU4OTM%3D)
|
- [Other projects using Lightning](https://github.com/williamFalcon/pytorch-lightning/network/dependents?package_id=UGFja2FnZS0zNzE3NDU4OTM%3D)
|
||||||
- [Multi-node](https://github.com/williamFalcon/pytorch-lightning/tree/master/examples/multi_node_examples)
|
- [Multi-node](https://github.com/williamFalcon/pytorch-lightning/tree/master/pl_examples/multi_node_examples)
|
||||||
|
|
||||||
## Tutorials
|
## Tutorials
|
||||||
- [Basic Lightning use](https://towardsdatascience.com/supercharge-your-ai-research-with-pytorch-lightning-337948a99eec)
|
- [Basic Lightning use](https://towardsdatascience.com/supercharge-your-ai-research-with-pytorch-lightning-337948a99eec)
|
||||||
@@ -392,7 +395,7 @@ Nope. Please use anaconda or miniconda.
|
|||||||
# install latest Lightning version without upgrading deps
|
# install latest Lightning version without upgrading deps
|
||||||
pip install -U --no-deps pytorch-lightning
|
pip install -U --no-deps pytorch-lightning
|
||||||
```
|
```
|
||||||
- **PyTorch 1.2.0**
|
- **PyTorch 1.2.0, 1.3.0,**
|
||||||
Install via pip as normal
|
Install via pip as normal
|
||||||
|
|
||||||
## Custom installation
|
## Custom installation
|
||||||
@@ -415,3 +418,16 @@ You can also install any past release from this repository:
|
|||||||
```bash
|
```bash
|
||||||
pip install https://github.com/williamFalcon/pytorch-lightning/archive/0.4.4.zip --upgrade
|
pip install https://github.com/williamFalcon/pytorch-lightning/archive/0.4.4.zip --upgrade
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Bibtex
|
||||||
|
If you want to cite the framework feel free to use this (but only if you loved it 😊):
|
||||||
|
```
|
||||||
|
@misc{Falcon2019,
|
||||||
|
author = {Falcon, W.A.},
|
||||||
|
title = {PyTorch Lightning},
|
||||||
|
year = {2019},
|
||||||
|
publisher = {GitHub},
|
||||||
|
journal = {GitHub repository},
|
||||||
|
howpublished = {\url{https://github.com/williamFalcon/pytorch-lightning}}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ Otherwise, to Define a Lightning Module, implement the following methods:
|
|||||||
|
|
||||||
**Optional**:
|
**Optional**:
|
||||||
|
|
||||||
|
- [training_end](RequiredTrainerInterface.md#training_end)
|
||||||
- [validation_step](RequiredTrainerInterface.md#validation_step)
|
- [validation_step](RequiredTrainerInterface.md#validation_step)
|
||||||
- [validation_end](RequiredTrainerInterface.md#validation_end)
|
- [validation_end](RequiredTrainerInterface.md#validation_end)
|
||||||
- [test_step](RequiredTrainerInterface.md#test_step)
|
- [test_step](RequiredTrainerInterface.md#test_step)
|
||||||
@@ -143,10 +144,15 @@ def training_step(self, batch, batch_nb):
|
|||||||
out = self.forward(x)
|
out = self.forward(x)
|
||||||
loss = self.loss(out, x)
|
loss = self.loss(out, x)
|
||||||
|
|
||||||
|
logger_logs = {'training_loss': loss} # optional (MUST ALL BE TENSORS)
|
||||||
|
|
||||||
|
# if using TestTubeLogger or TensorboardLogger you can nest scalars
|
||||||
|
logger_logs = {'losses': logger_logs} # optional (MUST ALL BE TENSORS)
|
||||||
|
|
||||||
output = {
|
output = {
|
||||||
'loss': loss, # required
|
'loss': loss, # required
|
||||||
'progress_bar': {'training_loss': loss}, # optional (MUST ALL BE TENSORS)
|
'progress_bar': {'training_loss': loss}, # optional (MUST ALL BE TENSORS)
|
||||||
'log': {'training_loss': loss} # optional (MUST ALL BE TENSORS)
|
'log': logger_logs
|
||||||
}
|
}
|
||||||
|
|
||||||
# return a dict
|
# return a dict
|
||||||
@@ -163,6 +169,96 @@ def training_step(self, batch, batch_nb, optimizer_idx):
|
|||||||
# do training_step with decoder
|
# do training_step with decoder
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you add truncated back propagation through time you will also get an additional argument with the hidden states of the previous step.
|
||||||
|
``` {.python}
|
||||||
|
# Truncated back-propagation through time
|
||||||
|
def training_step(self, batch, batch_nb, hiddens):
|
||||||
|
# hiddens are the hiddens from the previous truncated backprop step
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also return a -1 instead of a dict to stop the current loop. This is useful if you want to
|
||||||
|
break out of the current training epoch early.
|
||||||
|
|
||||||
|
---
|
||||||
|
### training_end
|
||||||
|
|
||||||
|
``` {.python}
|
||||||
|
def training_end(self, train_step_outputs)
|
||||||
|
```
|
||||||
|
In certain cases (dp, ddp2), you might want to use all outputs of every process to do something.
|
||||||
|
For instance, if using negative samples, you could run a batch via dp and use ALL the outputs
|
||||||
|
for a single softmax across the full batch (ie: the denominator would use the full batch).
|
||||||
|
|
||||||
|
In this case you should define training_end to perform those calculations.
|
||||||
|
|
||||||
|
|
||||||
|
**Params**
|
||||||
|
|
||||||
|
| Param | description |
|
||||||
|
|---|---|
|
||||||
|
| outputs | What you return in training_step.
|
||||||
|
|
||||||
|
**Return**
|
||||||
|
|
||||||
|
Dictionary or OrderedDict
|
||||||
|
|
||||||
|
| key | value | is required |
|
||||||
|
|---|---|---|
|
||||||
|
| loss | tensor scalar | Y |
|
||||||
|
| progress_bar | Dict for progress bar display. Must have only tensors | N |
|
||||||
|
| log | Dict of metrics to add to logger. Must have only tensors (no images, etc) | N |
|
||||||
|
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
``` {.python}
|
||||||
|
# WITHOUT training_end
|
||||||
|
# if used in DP or DDP2, this batch is 1/nb_gpus large
|
||||||
|
def training_step(self, batch, batch_nb):
|
||||||
|
# batch is 1/nb_gpus big
|
||||||
|
x, y = batch
|
||||||
|
|
||||||
|
out = self.forward(x)
|
||||||
|
loss = self.softmax(out)
|
||||||
|
loss = nce_loss(loss)
|
||||||
|
return {'loss': loss}
|
||||||
|
|
||||||
|
# --------------
|
||||||
|
# with training_end to do softmax over the full batch
|
||||||
|
def training_step(self, batch, batch_nb):
|
||||||
|
# batch is 1/nb_gpus big
|
||||||
|
x, y = batch
|
||||||
|
|
||||||
|
out = self.forward(x)
|
||||||
|
return {'out': out}
|
||||||
|
|
||||||
|
def training_end(self, outputs):
|
||||||
|
# this out is now the full size of the batch
|
||||||
|
out = outputs['out']
|
||||||
|
|
||||||
|
# this softmax now uses the full batch size
|
||||||
|
loss = self.softmax(out)
|
||||||
|
loss = nce_loss(loss)
|
||||||
|
return {'loss': loss}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you define multiple optimizers, this step will also be called with an additional ```optimizer_idx``` param.
|
||||||
|
``` {.python}
|
||||||
|
# Multiple optimizers (ie: GANs)
|
||||||
|
def training_step(self, batch, batch_nb, optimizer_idx):
|
||||||
|
if optimizer_idx == 0:
|
||||||
|
# do training_step with encoder
|
||||||
|
if optimizer_idx == 1:
|
||||||
|
# do training_step with decoder
|
||||||
|
```
|
||||||
|
|
||||||
|
If you add truncated back propagation through time you will also get an additional argument with the hidden states of the previous step.
|
||||||
|
``` {.python}
|
||||||
|
# Truncated back-propagation through time
|
||||||
|
def training_step(self, batch, batch_nb, hiddens):
|
||||||
|
# hiddens are the hiddens from the previous truncated backprop step
|
||||||
|
```
|
||||||
|
|
||||||
You can also return a -1 instead of a dict to stop the current loop. This is useful if you want to
|
You can also return a -1 instead of a dict to stop the current loop. This is useful if you want to
|
||||||
break out of the current training epoch early.
|
break out of the current training epoch early.
|
||||||
|
|
||||||
@@ -174,7 +270,7 @@ break out of the current training epoch early.
|
|||||||
def train_dataloader(self)
|
def train_dataloader(self)
|
||||||
```
|
```
|
||||||
Called by lightning during training loop. Make sure to use the @pl.data_loader decorator, this ensures not calling this function until the data are needed.
|
Called by lightning during training loop. Make sure to use the @pl.data_loader decorator, this ensures not calling this function until the data are needed.
|
||||||
If you want to change the data during every epoch DON'T use the data_loader decorator.
|
If you want to change the data during every epoch DON'T use the data_loader decorator.
|
||||||
|
|
||||||
##### Return
|
##### Return
|
||||||
PyTorch DataLoader
|
PyTorch DataLoader
|
||||||
@@ -318,12 +414,10 @@ The ```dataset_idx``` corresponds to the order of datasets returned in ```val_da
|
|||||||
``` {.python}
|
``` {.python}
|
||||||
def validation_end(self, outputs)
|
def validation_end(self, outputs)
|
||||||
```
|
```
|
||||||
If you didn't define a validation_step, this won't be called.
|
If you didn't define a validation_step, this won't be called. Called at the end of the validation loop with the outputs of validation_step.
|
||||||
|
|
||||||
Called at the end of the validation loop with the outputs of validation_step.
|
|
||||||
|
|
||||||
The outputs here are strictly for the progress bar. If you don't need to display anything, don't return anything.
|
The outputs here are strictly for the progress bar. If you don't need to display anything, don't return anything.
|
||||||
|
Any keys present in 'log', 'progress_bar' or the rest of the dictionary are available for callbacks to access.
|
||||||
**Params**
|
**Params**
|
||||||
|
|
||||||
| Param | description |
|
| Param | description |
|
||||||
|
|||||||
@@ -10,8 +10,25 @@ model.freeze()
|
|||||||
|
|
||||||
---
|
---
|
||||||
### load_from_metrics
|
### load_from_metrics
|
||||||
This is the easiest/fastest way which uses the meta_tags.csv file from test-tube to rebuild the model.
|
This is the easiest/fastest way which loads hyperparameters and weights from a checkpoint,
|
||||||
The meta_tags.csv file can be found in the test-tube experiment save_dir.
|
such as the one saved by the `ModelCheckpoint` callback
|
||||||
|
|
||||||
|
```{.python}
|
||||||
|
pretrained_model = MyLightningModule.load_from_checkpoint(
|
||||||
|
checkpoint_path='/path/to/pytorch_checkpoint.ckpt'
|
||||||
|
)
|
||||||
|
|
||||||
|
# predict
|
||||||
|
pretrained_model.eval()
|
||||||
|
pretrained_model.freeze()
|
||||||
|
y_hat = pretrained_model(x)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
### load_from_metrics
|
||||||
|
If you're using test tube, there is an alternate method which uses the meta_tags.csv
|
||||||
|
file from test-tube to rebuild the model. The meta_tags.csv file can be found in the
|
||||||
|
test-tube experiment save_dir.
|
||||||
|
|
||||||
```{.python}
|
```{.python}
|
||||||
pretrained_model = MyLightningModule.load_from_metrics(
|
pretrained_model = MyLightningModule.load_from_metrics(
|
||||||
|
|||||||
@@ -32,12 +32,19 @@ You might want to not only load a model but also continue training it. Use this
|
|||||||
restore the trainer state as well. This will continue from the epoch and global step you last left off.
|
restore the trainer state as well. This will continue from the epoch and global step you last left off.
|
||||||
However, the dataloaders will start from the first batch again (if you shuffled it shouldn't matter).
|
However, the dataloaders will start from the first batch again (if you shuffled it shouldn't matter).
|
||||||
|
|
||||||
Lightning will restore the session if you pass an experiment with the same version and there's a saved checkpoint.
|
Lightning will restore the session if you pass a logger with the same version and there's a saved checkpoint.
|
||||||
``` {.python}
|
``` {.python}
|
||||||
from test_tube import Experiment
|
from pytorch_lightning import Trainer
|
||||||
|
from pytorch_lightning.logging import TestTubeLogger
|
||||||
|
|
||||||
exp = Experiment(version=a_previous_version_with_a_saved_checkpoint)
|
logger = TestTubeLogger(
|
||||||
trainer = Trainer(experiment=exp)
|
save_dir='./savepath',
|
||||||
|
version=1 # An existing version with a saved checkpoint
|
||||||
|
)
|
||||||
|
trainer = Trainer(
|
||||||
|
logger=logger,
|
||||||
|
default_save_path='./savepath'
|
||||||
|
)
|
||||||
|
|
||||||
# this fit call loads model weights and trainer state
|
# this fit call loads model weights and trainer state
|
||||||
# the trainer continues seamlessly from where you left off
|
# the trainer continues seamlessly from where you left off
|
||||||
|
|||||||
@@ -58,14 +58,21 @@ Below are the possible configurations we support.
|
|||||||
You also have the option of specifying which GPUs to use by passing a list:
|
You also have the option of specifying which GPUs to use by passing a list:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# DEFAULT (int)
|
# DEFAULT (int) specifies how many GPUs to use.
|
||||||
Trainer(gpus=k)
|
Trainer(gpus=k)
|
||||||
|
|
||||||
|
# Above is equivalent to
|
||||||
|
Trainer(gpus=list(range(k)))
|
||||||
|
|
||||||
# You specify which GPUs (don't use if running on cluster)
|
# You specify which GPUs (don't use if running on cluster)
|
||||||
Trainer(gpus=[0, 1])
|
Trainer(gpus=[0, 1])
|
||||||
|
|
||||||
# can also be a string
|
# can also be a string
|
||||||
Trainer(gpus='0, 1')
|
Trainer(gpus='0, 1')
|
||||||
|
|
||||||
|
# can also be -1 or '-1', this uses all available GPUs
|
||||||
|
# this is equivalent to list(range(torch.cuda.available_devices()))
|
||||||
|
Trainer(gpus=-1)
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -201,7 +208,7 @@ Instead of manually building SLURM scripts, you can use the [SlurmCluster object
|
|||||||
do this for you. The SlurmCluster can also run a grid search if you pass in a [HyperOptArgumentParser](https://williamfalcon.github.io/test-tube/hyperparameter_optimization/HyperOptArgumentParser/).
|
do this for you. The SlurmCluster can also run a grid search if you pass in a [HyperOptArgumentParser](https://williamfalcon.github.io/test-tube/hyperparameter_optimization/HyperOptArgumentParser/).
|
||||||
|
|
||||||
Here is an example where you run a grid search of 9 combinations of hyperparams.
|
Here is an example where you run a grid search of 9 combinations of hyperparams.
|
||||||
[The full examples are here](https://github.com/williamFalcon/pytorch-lightning/tree/master/examples/new_project_templates/multi_node_examples).
|
[The full examples are here](https://github.com/williamFalcon/pytorch-lightning/tree/master/pl_examples/new_project_templates/multi_node_examples).
|
||||||
```python
|
```python
|
||||||
# grid search 3 values of learning rate and 3 values of number of layers for your net
|
# grid search 3 values of learning rate and 3 values of number of layers for your net
|
||||||
# this generates 9 experiments (lr=1e-3, layers=16), (lr=1e-3, layers=32), (lr=1e-3, layers=64), ... (lr=1e-1, layers=64)
|
# this generates 9 experiments (lr=1e-3, layers=16), (lr=1e-3, layers=32), (lr=1e-3, layers=64), ... (lr=1e-1, layers=64)
|
||||||
|
|||||||
+53
-6
@@ -33,7 +33,10 @@ Lightning supports several common experiment tracking frameworks out of the box
|
|||||||
---
|
---
|
||||||
#### Test tube
|
#### Test tube
|
||||||
|
|
||||||
Log using [test tube](https://williamfalcon.github.io/test-tube/).
|
Log using [test tube](https://williamfalcon.github.io/test-tube/). Test tube logger is
|
||||||
|
a strict subclass of [PyTorch SummaryWriter](https://pytorch.org/docs/stable/tensorboard.html), refer to their
|
||||||
|
documentation for all supported operations. The TestTubeLogger adds a nicer folder structure
|
||||||
|
to manage experiments and snapshots all hyperparameters you pass to a LightningModule.
|
||||||
|
|
||||||
```{.python}
|
```{.python}
|
||||||
from pytorch_lightning.logging import TestTubeLogger
|
from pytorch_lightning.logging import TestTubeLogger
|
||||||
@@ -46,6 +49,16 @@ tt_logger = TestTubeLogger(
|
|||||||
trainer = Trainer(logger=tt_logger)
|
trainer = Trainer(logger=tt_logger)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Use the logger anywhere in you LightningModule as follows:
|
||||||
|
```python
|
||||||
|
def train_step(...):
|
||||||
|
# example
|
||||||
|
self.logger.experiment.whatever_method_summary_writer_supports(...)
|
||||||
|
|
||||||
|
def any_lightning_module_function_or_hook(...):
|
||||||
|
self.logger.experiment.add_histogram(...)
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
#### MLFlow
|
#### MLFlow
|
||||||
|
|
||||||
@@ -59,6 +72,39 @@ mlf_logger = MLFlowLogger(
|
|||||||
)
|
)
|
||||||
trainer = Trainer(logger=mlf_logger)
|
trainer = Trainer(logger=mlf_logger)
|
||||||
```
|
```
|
||||||
|
Use the logger anywhere in you LightningModule as follows:
|
||||||
|
```python
|
||||||
|
def train_step(...):
|
||||||
|
# example
|
||||||
|
self.logger.experiment.whatever_ml_flow_supports(...)
|
||||||
|
|
||||||
|
def any_lightning_module_function_or_hook(...):
|
||||||
|
self.logger.experiment.whatever_ml_flow_supports(...)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
#### Comet.ml
|
||||||
|
|
||||||
|
Log using [comet](https://www.comet.ml)
|
||||||
|
|
||||||
|
```{.python}
|
||||||
|
from pytorch_lightning.logging import CometLogger
|
||||||
|
# arguments made to CometLogger are passed on to the comet_ml.Experiment class
|
||||||
|
comet_logger = CometLogger(
|
||||||
|
api_key=os.environ["COMET_KEY"],
|
||||||
|
workspace=os.environ["COMET_KEY"],
|
||||||
|
)
|
||||||
|
trainer = Trainer(logger=comet_logger)
|
||||||
|
```
|
||||||
|
Use the logger anywhere in you LightningModule as follows:
|
||||||
|
```python
|
||||||
|
def train_step(...):
|
||||||
|
# example
|
||||||
|
self.logger.experiment.whatever_comet_ml_supports(...)
|
||||||
|
|
||||||
|
def any_lightning_module_function_or_hook(...):
|
||||||
|
self.logger.experiment.whatever_comet_ml_supports(...)
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
#### Custom logger
|
#### Custom logger
|
||||||
@@ -101,13 +147,14 @@ a pull request to add it to Lighting!
|
|||||||
#### Using loggers
|
#### Using loggers
|
||||||
You can call the logger anywhere from your LightningModule by doing:
|
You can call the logger anywhere from your LightningModule by doing:
|
||||||
```python
|
```python
|
||||||
self.logger
|
def train_step(...):
|
||||||
|
# example
|
||||||
# add an image if using TestTubeLogger
|
self.logger.experiment.whatever_method_summary_writer_supports(...)
|
||||||
self.logger.experiment.add_image(...)
|
|
||||||
|
def any_lightning_module_function_or_hook(...):
|
||||||
|
self.logger.experiment.add_histogram(...)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
#### Display metrics in progress bar
|
#### Display metrics in progress bar
|
||||||
``` {.python}
|
``` {.python}
|
||||||
# DEFAULT
|
# DEFAULT
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ The lightning training loop handles everything except the actual computations of
|
|||||||
Below are all the things lightning automates for you in the training loop.
|
Below are all the things lightning automates for you in the training loop.
|
||||||
|
|
||||||
---
|
---
|
||||||
#### Accumulated gradients
|
#### Accumulated gradients
|
||||||
Accumulated gradients runs K small batches of size N before doing a backwards pass. The effect is a large effective batch size of size KxN.
|
Accumulated gradients runs K small batches of size N before doing a backwards pass. The effect is a large effective batch size of size KxN.
|
||||||
|
|
||||||
``` {.python}
|
``` {.python}
|
||||||
# DEFAULT (ie: no accumulated grads)
|
# DEFAULT (ie: no accumulated grads)
|
||||||
@@ -21,7 +21,7 @@ trainer = Trainer(min_nb_epochs=1, max_nb_epochs=1000)
|
|||||||
|
|
||||||
---
|
---
|
||||||
#### Early stopping
|
#### Early stopping
|
||||||
The trainer already sets up default early stopping for you.
|
The trainer already sets up default early stopping for you.
|
||||||
To modify this behavior, pass in your own EarlyStopping callback.
|
To modify this behavior, pass in your own EarlyStopping callback.
|
||||||
``` {.python}
|
``` {.python}
|
||||||
from pytorch_lightning.callbacks import EarlyStopping
|
from pytorch_lightning.callbacks import EarlyStopping
|
||||||
@@ -35,15 +35,22 @@ early_stop_callback = EarlyStopping(
|
|||||||
mode='min'
|
mode='min'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# without passing anything in, uses the default callback above
|
||||||
|
trainer = Trainer()
|
||||||
|
|
||||||
|
# pass in your own to override the default callback
|
||||||
trainer = Trainer(early_stop_callback=early_stop_callback)
|
trainer = Trainer(early_stop_callback=early_stop_callback)
|
||||||
|
|
||||||
|
# pass in None to disable it
|
||||||
|
trainer = Trainer(early_stop_callback=None)
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
#### Force disable early stop
|
#### Force disable early stop
|
||||||
Use this to turn off early stopping and run training to the [max_epoch](#force-training-for-min-or-max-epochs)
|
To disable early stopping pass None to the early_stop_callback
|
||||||
``` {.python}
|
``` {.python}
|
||||||
# DEFAULT
|
# DEFAULT
|
||||||
trainer = Trainer(enable_early_stop=True)
|
trainer = Trainer(early_stop_callback=None)
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -84,3 +91,36 @@ trainer = Trainer(train_percent_check=1.0)
|
|||||||
# check 10% only
|
# check 10% only
|
||||||
trainer = Trainer(train_percent_check=0.1)
|
trainer = Trainer(train_percent_check=0.1)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
#### Packed sequences as inputs
|
||||||
|
When using PackedSequence, do 2 things:
|
||||||
|
1. return either a padded tensor in dataset or a list of variable length tensors in the dataloader collate_fn (example above shows the list implementation).
|
||||||
|
2. Pack the sequence in forward or training and validation steps depending on use case.
|
||||||
|
|
||||||
|
``` {.python}
|
||||||
|
# For use in dataloader
|
||||||
|
def collate_fn(batch):
|
||||||
|
x = [item[0] for item in batch]
|
||||||
|
y = [item[1] for item in batch]
|
||||||
|
return x, y
|
||||||
|
|
||||||
|
# In module
|
||||||
|
def training_step(self, batch, batch_nb):
|
||||||
|
x = rnn.pack_sequence(batch[0], enforce_sorted=False)
|
||||||
|
y = rnn.pack_sequence(batch[1], enforce_sorted=False)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
#### Truncated Back Propagation Through Time
|
||||||
|
There are times when multiple backwards passes are needed for each batch. For example, it may save memory to use Truncated Back Propagation Through Time when training RNNs.
|
||||||
|
|
||||||
|
When this flag is enabled each batch is split into sequences of size truncated_bptt_steps and passed to training_step(...) separately. A default splitting function is provided, however, you can override it for more flexibility. See [tbptt_split_batch](https://williamfalcon.github.io/pytorch-lightning/Trainer/hooks#tbptt_split_batch).
|
||||||
|
|
||||||
|
``` {.python}
|
||||||
|
# DEFAULT (single backwards pass per batch)
|
||||||
|
trainer = Trainer(truncated_bptt_steps=None)
|
||||||
|
|
||||||
|
# (split batch into sequences of size 2)
|
||||||
|
trainer = Trainer(truncated_bptt_steps=2)
|
||||||
|
```
|
||||||
|
|||||||
@@ -43,13 +43,20 @@ trainer = Trainer(test_percent_check=0.1)
|
|||||||
|
|
||||||
---
|
---
|
||||||
#### Set validation check frequency within 1 training epoch
|
#### Set validation check frequency within 1 training epoch
|
||||||
For large datasets it's often desirable to check validation multiple times within a training loop
|
For large datasets it's often desirable to check validation multiple times within a training loop.
|
||||||
|
Pass in a float to check that often within 1 training epoch.
|
||||||
|
Pass in an int k to check every k training batches. Must use an int if using
|
||||||
|
an IterableDataset.
|
||||||
|
|
||||||
``` {.python}
|
``` {.python}
|
||||||
# DEFAULT
|
# DEFAULT
|
||||||
trainer = Trainer(val_check_interval=0.95)
|
trainer = Trainer(val_check_interval=0.95)
|
||||||
|
|
||||||
# check every .25 of an epoch
|
# check every .25 of an epoch
|
||||||
trainer = Trainer(val_check_interval=0.25)
|
trainer = Trainer(val_check_interval=0.25)
|
||||||
|
|
||||||
|
# check every 100 train batches (ie: for IterableDatasets or fixed frequency)
|
||||||
|
trainer = Trainer(val_check_interval=100)
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -38,6 +38,14 @@ trainer = Trainer(overfit_pct=0.01)
|
|||||||
#### Print the parameter count by layer
|
#### Print the parameter count by layer
|
||||||
By default lightning prints a list of parameters *and submodules* when it starts training.
|
By default lightning prints a list of parameters *and submodules* when it starts training.
|
||||||
|
|
||||||
|
``` {.python}
|
||||||
|
# DEFAULT print a full list of all submodules and their parameters.
|
||||||
|
trainer = Trainer(weights_summary='full')
|
||||||
|
|
||||||
|
# only print the top-level modules (i.e. the children of LightningModule).
|
||||||
|
trainer = Trainer(weights_summary='top')
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
#### Print which gradients are nan
|
#### Print which gradients are nan
|
||||||
This option prints a list of tensors with nan gradients.
|
This option prints a list of tensors with nan gradients.
|
||||||
|
|||||||
@@ -115,6 +115,28 @@ def on_before_zero_grad(self, optimizer):
|
|||||||
# do something with the optimizer or inspect it.
|
# do something with the optimizer or inspect it.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
#### backward
|
||||||
|
Called to perform backward step.
|
||||||
|
Feel free to override as needed.
|
||||||
|
|
||||||
|
The loss passed in has already been scaled for accumulated gradients if requested.
|
||||||
|
```python
|
||||||
|
def backward(self, use_amp, loss, optimizer):
|
||||||
|
"""
|
||||||
|
Override backward with your own implementation if you need to
|
||||||
|
:param use_amp: Whether amp was requested or not
|
||||||
|
:param loss: Loss is already scaled by accumulated grads
|
||||||
|
:param optimizer: Current optimizer being used
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if use_amp:
|
||||||
|
with amp.scale_loss(loss, optimizer) as scaled_loss:
|
||||||
|
scaled_loss.backward()
|
||||||
|
else:
|
||||||
|
loss.backward()
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
#### on_after_backward
|
#### on_after_backward
|
||||||
Called in the training loop after model.backward()
|
Called in the training loop after model.backward()
|
||||||
@@ -129,3 +151,116 @@ def on_after_backward(self):
|
|||||||
name = k
|
name = k
|
||||||
self.logger.experiment.add_histogram(tag=name, values=grads, global_step=self.trainer.global_step)
|
self.logger.experiment.add_histogram(tag=name, values=grads, global_step=self.trainer.global_step)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
#### tbptt_split_batch
|
||||||
|
Called in the training loop after on_batch_start if `truncated_bptt_steps > 0`. Each returned batch split is passed separately to training_step(...).
|
||||||
|
|
||||||
|
```python
|
||||||
|
def tbptt_split_batch(self, batch, split_size):
|
||||||
|
splits = []
|
||||||
|
for t in range(0, time_dims[0], split_size):
|
||||||
|
batch_split = []
|
||||||
|
for i, x in enumerate(batch):
|
||||||
|
if isinstance(x, torch.Tensor):
|
||||||
|
split_x = x[:, t:t + split_size]
|
||||||
|
elif isinstance(x, collections.Sequence):
|
||||||
|
split_x = [None] * len(x)
|
||||||
|
for batch_idx in range(len(x)):
|
||||||
|
split_x[batch_idx] = x[batch_idx][t:t + split_size]
|
||||||
|
|
||||||
|
batch_split.append(split_x)
|
||||||
|
|
||||||
|
splits.append(batch_split)
|
||||||
|
|
||||||
|
return splits
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
#### configure_apex
|
||||||
|
Overwrite to define your own Apex implementation init.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def configure_apex(self, amp, model, optimizers, amp_level):
|
||||||
|
"""
|
||||||
|
Override to init AMP your own way
|
||||||
|
Must return a model and list of optimizers
|
||||||
|
:param amp:
|
||||||
|
:param model:
|
||||||
|
:param optimizers:
|
||||||
|
:param amp_level:
|
||||||
|
:return: Apex wrapped model and optimizers
|
||||||
|
"""
|
||||||
|
model, optimizers = amp.initialize(
|
||||||
|
model, optimizers, opt_level=amp_level,
|
||||||
|
)
|
||||||
|
|
||||||
|
return model, optimizers
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
#### configure_ddp
|
||||||
|
Overwrite to define your own DDP implementation init.
|
||||||
|
The only requirement is that:
|
||||||
|
1. On a validation batch the call goes to model.validation_step.
|
||||||
|
2. On a training batch the call goes to model.training_step.
|
||||||
|
3. On a testing batch, the call goes to model.test_step
|
||||||
|
|
||||||
|
```python
|
||||||
|
def configure_ddp(self, model, device_ids):
|
||||||
|
"""
|
||||||
|
Override to init DDP in a different way or use your own wrapper.
|
||||||
|
Must return model.
|
||||||
|
:param model:
|
||||||
|
:param device_ids:
|
||||||
|
:return: DDP wrapped model
|
||||||
|
"""
|
||||||
|
# Lightning DDP simply routes to test_step, val_step, etc...
|
||||||
|
model = LightningDistributedDataParallel(
|
||||||
|
model,
|
||||||
|
device_ids=device_ids,
|
||||||
|
find_unused_parameters=True
|
||||||
|
)
|
||||||
|
return model
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
#### init_ddp_connection
|
||||||
|
Override to init DDP in your own way.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def init_ddp_connection(self):
|
||||||
|
"""
|
||||||
|
Connect all procs in the world using the env:// init
|
||||||
|
Use the first node as the root address
|
||||||
|
"""
|
||||||
|
|
||||||
|
# use slurm job id for the port number
|
||||||
|
# guarantees unique ports across jobs from same grid search
|
||||||
|
try:
|
||||||
|
# use the last 4 numbers in the job id as the id
|
||||||
|
default_port = os.environ['SLURM_JOB_ID']
|
||||||
|
default_port = default_port[-4:]
|
||||||
|
|
||||||
|
# all ports should be in the 10k+ range
|
||||||
|
default_port = int(default_port) + 15000
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
default_port = 12910
|
||||||
|
|
||||||
|
# if user gave a port number, use that one instead
|
||||||
|
try:
|
||||||
|
default_port = os.environ['MASTER_PORT']
|
||||||
|
except Exception:
|
||||||
|
os.environ['MASTER_PORT'] = str(default_port)
|
||||||
|
|
||||||
|
# figure out the root node addr
|
||||||
|
try:
|
||||||
|
root_node = os.environ['SLURM_NODELIST'].split(' ')[0]
|
||||||
|
except Exception:
|
||||||
|
root_node = '127.0.0.2'
|
||||||
|
|
||||||
|
root_node = self.trainer.resolve_root_node_address(root_node)
|
||||||
|
os.environ['MASTER_ADDR'] = root_node
|
||||||
|
dist.init_process_group('nccl', rank=self.proc_rank, world_size=self.world_size)
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Trainer
|
# Trainer
|
||||||
[[Github Code](https://github.com/williamFalcon/pytorch-lightning/blob/master/pytorch_lightning/models/trainer.py)]
|
[[Github Code](https://github.com/williamFalcon/pytorch-lightning/blob/master/pytorch_lightning/trainer/trainer.py)]
|
||||||
|
|
||||||
The lightning trainer abstracts best practices for running a training, val, test routine. It calls parts of your model when it wants to hand over full control and otherwise makes training assumptions which are now standard practice in AI research.
|
The lightning trainer abstracts best practices for running a training, val, test routine. It calls parts of your model when it wants to hand over full control and otherwise makes training assumptions which are now standard practice in AI research.
|
||||||
|
|
||||||
@@ -42,6 +42,7 @@ But of course the fun is in all the advanced things it can do:
|
|||||||
|
|
||||||
**Distributed training**
|
**Distributed training**
|
||||||
|
|
||||||
|
- [Implement Your Own Distributed (DDP) training](https://williamfalcon.github.io/pytorch-lightning/Trainer/hooks/#init_ddp_connection)
|
||||||
- [16-bit mixed precision](https://williamfalcon.github.io/pytorch-lightning/Trainer/Distributed%20training/#16-bit-mixed-precision)
|
- [16-bit mixed precision](https://williamfalcon.github.io/pytorch-lightning/Trainer/Distributed%20training/#16-bit-mixed-precision)
|
||||||
- [Multi-GPU](https://williamfalcon.github.io/pytorch-lightning/Trainer/Distributed%20training/#Multi-GPU)
|
- [Multi-GPU](https://williamfalcon.github.io/pytorch-lightning/Trainer/Distributed%20training/#Multi-GPU)
|
||||||
- [Multi-node](https://williamfalcon.github.io/pytorch-lightning/Trainer/Distributed%20training/#Multi-node)
|
- [Multi-node](https://williamfalcon.github.io/pytorch-lightning/Trainer/Distributed%20training/#Multi-node)
|
||||||
@@ -71,6 +72,8 @@ But of course the fun is in all the advanced things it can do:
|
|||||||
- [Use multiple optimizers (like GANs)](https://williamfalcon.github.io/pytorch-lightning/LightningModule/RequiredTrainerInterface/#configure_optimizers)
|
- [Use multiple optimizers (like GANs)](https://williamfalcon.github.io/pytorch-lightning/LightningModule/RequiredTrainerInterface/#configure_optimizers)
|
||||||
- [Set how much of the training set to check (1-100%)](https://williamfalcon.github.io/pytorch-lightning/Trainer/Training%20Loop/#set-how-much-of-the-training-set-to-check)
|
- [Set how much of the training set to check (1-100%)](https://williamfalcon.github.io/pytorch-lightning/Trainer/Training%20Loop/#set-how-much-of-the-training-set-to-check)
|
||||||
- [Step optimizers at arbitrary intervals](https://williamfalcon.github.io/pytorch-lightning/Trainer/hooks/#optimizer_step)
|
- [Step optimizers at arbitrary intervals](https://williamfalcon.github.io/pytorch-lightning/Trainer/hooks/#optimizer_step)
|
||||||
|
- [Packed sequences](https://williamfalcon.github.io/pytorch-lightning/Trainer/Training%20Loop/#packed-sequences-as-inputs)
|
||||||
|
- [Truncated Back Propagation Through Time](https://williamfalcon.github.io/pytorch-lightning//Training%20Loop/#truncated-back-propation-through-time)
|
||||||
|
|
||||||
**Validation loop**
|
**Validation loop**
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
### Template model definition
|
### Template model definition
|
||||||
In 99% of cases you want to just copy [this template](https://github.com/williamFalcon/pytorch-lightning/blob/master/examples/new_project_templates/lightning_module_template.py) to start a new lightningModule and change the core of what your model is actually trying to do.
|
In 99% of cases you want to just copy [one of the examples](https://github.com/williamFalcon/pytorch-lightning/tree/master/pl_examples) to start a new lightningModule and change the core of what your model is actually trying to do.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# get a copy of the module template
|
# get a copy of the module template
|
||||||
wget https://raw.githubusercontent.com/williamFalcon/pytorch-lightning/master/examples/new_project_templates/lightning_module_template.py
|
wget https://raw.githubusercontent.com/williamFalcon/pytorch-lightning/master/pl_examples/new_project_templates/lightning_module_template.py
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -119,7 +119,7 @@ def optimize_on_cluster(hyperparams):
|
|||||||
job_display_name = job_display_name[0:3]
|
job_display_name = job_display_name[0:3]
|
||||||
|
|
||||||
# run hopt
|
# run hopt
|
||||||
print('submitting jobs...')
|
logging.info('submitting jobs...')
|
||||||
cluster.optimize_parallel_cluster_gpu(
|
cluster.optimize_parallel_cluster_gpu(
|
||||||
main,
|
main,
|
||||||
nb_trials=hyperparams.nb_hopt_trials,
|
nb_trials=hyperparams.nb_hopt_trials,
|
||||||
|
|||||||
+3
-2
@@ -60,8 +60,8 @@ Notice a few things about this flow:
|
|||||||
###### Templates
|
###### Templates
|
||||||
1. [MNIST LightningModule](https://williamfalcon.github.io/pytorch-lightning/LightningModule/RequiredTrainerInterface/#minimal-example)
|
1. [MNIST LightningModule](https://williamfalcon.github.io/pytorch-lightning/LightningModule/RequiredTrainerInterface/#minimal-example)
|
||||||
2. [Trainer](https://williamfalcon.github.io/pytorch-lightning/Trainer/)
|
2. [Trainer](https://williamfalcon.github.io/pytorch-lightning/Trainer/)
|
||||||
- [Basic CPU, GPU Trainer Template](https://github.com/williamFalcon/pytorch-lightning/tree/master/examples/basic_examples)
|
- [Basic CPU, GPU Trainer Template](https://github.com/williamFalcon/pytorch-lightning/tree/master/pl_examples/basic_examples)
|
||||||
- [GPU cluster Trainer Template](https://github.com/williamFalcon/pytorch-lightning/tree/master/examples/multi_node_examples)
|
- [GPU cluster Trainer Template](https://github.com/williamFalcon/pytorch-lightning/tree/master/pl_examples/multi_node_examples)
|
||||||
|
|
||||||
###### Docs shortcuts
|
###### Docs shortcuts
|
||||||
- [LightningModule](LightningModule/RequiredTrainerInterface/)
|
- [LightningModule](LightningModule/RequiredTrainerInterface/)
|
||||||
@@ -99,6 +99,7 @@ Notice a few things about this flow:
|
|||||||
|
|
||||||
###### Distributed training
|
###### Distributed training
|
||||||
|
|
||||||
|
- [Implement Your Own Distributed (DDP) training](https://williamfalcon.github.io/pytorch-lightning/Trainer/hooks/#init_ddp_connection)
|
||||||
- [16-bit mixed precision](https://williamfalcon.github.io/pytorch-lightning/Trainer/Distributed%20training/#16-bit-mixed-precision)
|
- [16-bit mixed precision](https://williamfalcon.github.io/pytorch-lightning/Trainer/Distributed%20training/#16-bit-mixed-precision)
|
||||||
- [Multi-GPU](https://williamfalcon.github.io/pytorch-lightning/Trainer/Distributed%20training/#Multi-GPU)
|
- [Multi-GPU](https://williamfalcon.github.io/pytorch-lightning/Trainer/Distributed%20training/#Multi-GPU)
|
||||||
- [Multi-node](https://williamfalcon.github.io/pytorch-lightning/Trainer/Distributed%20training/#Multi-node)
|
- [Multi-node](https://williamfalcon.github.io/pytorch-lightning/Trainer/Distributed%20training/#Multi-node)
|
||||||
|
|||||||
+3
-2
@@ -2,12 +2,13 @@
|
|||||||
Runs a model on a single node across N-gpus.
|
Runs a model on a single node across N-gpus.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
from argparse import ArgumentParser
|
from pl_examples.basic_examples.lightning_module_template import LightningTemplateModel
|
||||||
from pytorch_lightning import Trainer
|
from pytorch_lightning import Trainer
|
||||||
from examples.basic_examples.lightning_module_template import LightningTemplateModel
|
|
||||||
|
|
||||||
SEED = 2334
|
SEED = 2334
|
||||||
torch.manual_seed(SEED)
|
torch.manual_seed(SEED)
|
||||||
+3
-2
@@ -2,12 +2,13 @@
|
|||||||
Runs a model on a single node across N-gpus.
|
Runs a model on a single node across N-gpus.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
from argparse import ArgumentParser
|
from pl_examples.basic_examples.lightning_module_template import LightningTemplateModel
|
||||||
from pytorch_lightning import Trainer
|
from pytorch_lightning import Trainer
|
||||||
from examples.basic_examples.lightning_module_template import LightningTemplateModel
|
|
||||||
|
|
||||||
SEED = 2334
|
SEED = 2334
|
||||||
torch.manual_seed(SEED)
|
torch.manual_seed(SEED)
|
||||||
+17
-12
@@ -2,16 +2,18 @@
|
|||||||
Example template for defining a system
|
Example template for defining a system
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
from collections import OrderedDict
|
import logging
|
||||||
import torch.nn as nn
|
|
||||||
from torchvision.datasets import MNIST
|
|
||||||
import torchvision.transforms as transforms
|
|
||||||
import torch
|
|
||||||
import torch.nn.functional as F
|
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
import torch
|
||||||
|
import torch.nn as nn
|
||||||
|
import torch.nn.functional as F
|
||||||
|
import torchvision.transforms as transforms
|
||||||
from torch import optim
|
from torch import optim
|
||||||
from torch.utils.data import DataLoader
|
from torch.utils.data import DataLoader
|
||||||
from torch.utils.data.distributed import DistributedSampler
|
from torch.utils.data.distributed import DistributedSampler
|
||||||
|
from torchvision.datasets import MNIST
|
||||||
|
|
||||||
import pytorch_lightning as pl
|
import pytorch_lightning as pl
|
||||||
from pytorch_lightning.root_module.root_module import LightningModule
|
from pytorch_lightning.root_module.root_module import LightningModule
|
||||||
@@ -98,8 +100,11 @@ class LightningTemplateModel(LightningModule):
|
|||||||
if self.trainer.use_dp or self.trainer.use_ddp2:
|
if self.trainer.use_dp or self.trainer.use_ddp2:
|
||||||
loss_val = loss_val.unsqueeze(0)
|
loss_val = loss_val.unsqueeze(0)
|
||||||
|
|
||||||
|
tqdm_dict = {'train_loss': loss_val}
|
||||||
output = OrderedDict({
|
output = OrderedDict({
|
||||||
'loss': loss_val
|
'loss': loss_val,
|
||||||
|
'progress_bar': tqdm_dict,
|
||||||
|
'log': tqdm_dict
|
||||||
})
|
})
|
||||||
|
|
||||||
# can also return just a scalar instead of a dict (return loss_val)
|
# can also return just a scalar instead of a dict (return loss_val)
|
||||||
@@ -154,7 +159,7 @@ class LightningTemplateModel(LightningModule):
|
|||||||
val_loss = output['val_loss']
|
val_loss = output['val_loss']
|
||||||
|
|
||||||
# reduce manually when using dp
|
# reduce manually when using dp
|
||||||
if self.trainer.use_dp:
|
if self.trainer.use_dp or self.trainer.use_ddp2:
|
||||||
val_loss = torch.mean(val_loss)
|
val_loss = torch.mean(val_loss)
|
||||||
val_loss_mean += val_loss
|
val_loss_mean += val_loss
|
||||||
|
|
||||||
@@ -168,7 +173,7 @@ class LightningTemplateModel(LightningModule):
|
|||||||
val_loss_mean /= len(outputs)
|
val_loss_mean /= len(outputs)
|
||||||
val_acc_mean /= len(outputs)
|
val_acc_mean /= len(outputs)
|
||||||
tqdm_dict = {'val_loss': val_loss_mean, 'val_acc': val_acc_mean}
|
tqdm_dict = {'val_loss': val_loss_mean, 'val_acc': val_acc_mean}
|
||||||
result = {'progress_bar': tqdm_dict, 'logs': tqdm_dict}
|
result = {'progress_bar': tqdm_dict, 'log': tqdm_dict, 'val_loss': val_loss_mean}
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# ---------------------
|
# ---------------------
|
||||||
@@ -210,17 +215,17 @@ class LightningTemplateModel(LightningModule):
|
|||||||
|
|
||||||
@pl.data_loader
|
@pl.data_loader
|
||||||
def train_dataloader(self):
|
def train_dataloader(self):
|
||||||
print('training data loader called')
|
logging.info('training data loader called')
|
||||||
return self.__dataloader(train=True)
|
return self.__dataloader(train=True)
|
||||||
|
|
||||||
@pl.data_loader
|
@pl.data_loader
|
||||||
def val_dataloader(self):
|
def val_dataloader(self):
|
||||||
print('val data loader called')
|
logging.info('val data loader called')
|
||||||
return self.__dataloader(train=False)
|
return self.__dataloader(train=False)
|
||||||
|
|
||||||
@pl.data_loader
|
@pl.data_loader
|
||||||
def test_dataloader(self):
|
def test_dataloader(self):
|
||||||
print('test data loader called')
|
logging.info('test data loader called')
|
||||||
return self.__dataloader(train=False)
|
return self.__dataloader(train=False)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -1,27 +1,25 @@
|
|||||||
"""
|
"""
|
||||||
To run this template just do:
|
To run this template just do:
|
||||||
python gan.py
|
python gan.py
|
||||||
|
|
||||||
After a few epochs, launch tensorboard to see the images being generated at every batch.
|
After a few epochs, launch tensorboard to see the images being generated at every batch.
|
||||||
|
|
||||||
tensorboard --logdir default
|
tensorboard --logdir default
|
||||||
"""
|
"""
|
||||||
from argparse import ArgumentParser
|
|
||||||
import os
|
import os
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import torch
|
||||||
import torchvision
|
|
||||||
import torchvision.transforms as transforms
|
|
||||||
from torchvision.datasets import MNIST
|
|
||||||
|
|
||||||
from torch.utils.data import DataLoader
|
|
||||||
|
|
||||||
import torch.nn as nn
|
import torch.nn as nn
|
||||||
import torch.nn.functional as F
|
import torch.nn.functional as F
|
||||||
import torch
|
import torchvision
|
||||||
|
import torchvision.transforms as transforms
|
||||||
|
from torch.utils.data import DataLoader
|
||||||
|
from torchvision.datasets import MNIST
|
||||||
|
|
||||||
import pytorch_lightning as pl
|
import pytorch_lightning as pl
|
||||||
from test_tube import Experiment
|
|
||||||
|
|
||||||
|
|
||||||
class Generator(nn.Module):
|
class Generator(nn.Module):
|
||||||
@@ -84,6 +82,7 @@ class GAN(pl.LightningModule):
|
|||||||
|
|
||||||
# cache for generated images
|
# cache for generated images
|
||||||
self.generated_imgs = None
|
self.generated_imgs = None
|
||||||
|
self.last_imgs = None
|
||||||
|
|
||||||
def forward(self, z):
|
def forward(self, z):
|
||||||
return self.generator(z)
|
return self.generator(z)
|
||||||
@@ -93,6 +92,7 @@ class GAN(pl.LightningModule):
|
|||||||
|
|
||||||
def training_step(self, batch, batch_nb, optimizer_i):
|
def training_step(self, batch, batch_nb, optimizer_i):
|
||||||
imgs, _ = batch
|
imgs, _ = batch
|
||||||
|
self.last_imgs = imgs
|
||||||
|
|
||||||
# train generator
|
# train generator
|
||||||
if optimizer_i == 0:
|
if optimizer_i == 0:
|
||||||
@@ -107,17 +107,22 @@ class GAN(pl.LightningModule):
|
|||||||
self.generated_imgs = self.forward(z)
|
self.generated_imgs = self.forward(z)
|
||||||
|
|
||||||
# log sampled images
|
# log sampled images
|
||||||
sample_imgs = self.generated_imgs[:6]
|
# sample_imgs = self.generated_imgs[:6]
|
||||||
grid = torchvision.utils.make_grid(sample_imgs)
|
# grid = torchvision.utils.make_grid(sample_imgs)
|
||||||
self.logger.experiment.add_image('generated_images', grid, 0)
|
# self.logger.experiment.add_image('generated_images', grid, 0)
|
||||||
|
|
||||||
# ground truth result (ie: all fake)
|
# ground truth result (ie: all fake)
|
||||||
valid = torch.ones(imgs.size(0), 1)
|
valid = torch.ones(imgs.size(0), 1)
|
||||||
|
|
||||||
# adversarial loss is binary cross-entropy
|
# adversarial loss is binary cross-entropy
|
||||||
g_loss = self.adversarial_loss(self.discriminator(self.generated_imgs), valid)
|
g_loss = self.adversarial_loss(self.discriminator(self.generated_imgs), valid)
|
||||||
|
tqdm_dict = {'g_loss': g_loss}
|
||||||
return g_loss
|
output = OrderedDict({
|
||||||
|
'loss': g_loss,
|
||||||
|
'progress_bar': tqdm_dict,
|
||||||
|
'log': tqdm_dict
|
||||||
|
})
|
||||||
|
return output
|
||||||
|
|
||||||
# train discriminator
|
# train discriminator
|
||||||
if optimizer_i == 1:
|
if optimizer_i == 1:
|
||||||
@@ -129,12 +134,18 @@ class GAN(pl.LightningModule):
|
|||||||
|
|
||||||
# how well can it label as fake?
|
# how well can it label as fake?
|
||||||
fake = torch.zeros(imgs.size(0), 1)
|
fake = torch.zeros(imgs.size(0), 1)
|
||||||
fake_loss = self.adversarial_loss(self.discriminator(self.generated_imgs.detach()), fake)
|
fake_loss = self.adversarial_loss(
|
||||||
|
self.discriminator(self.generated_imgs.detach()), fake)
|
||||||
|
|
||||||
# discriminator loss is the average of these
|
# discriminator loss is the average of these
|
||||||
d_loss = (real_loss + fake_loss) / 2
|
d_loss = (real_loss + fake_loss) / 2
|
||||||
|
tqdm_dict = {'d_loss': d_loss}
|
||||||
return d_loss
|
output = OrderedDict({
|
||||||
|
'loss': d_loss,
|
||||||
|
'progress_bar': tqdm_dict,
|
||||||
|
'log': tqdm_dict
|
||||||
|
})
|
||||||
|
return output
|
||||||
|
|
||||||
def configure_optimizers(self):
|
def configure_optimizers(self):
|
||||||
lr = self.hparams.lr
|
lr = self.hparams.lr
|
||||||
@@ -152,16 +163,32 @@ class GAN(pl.LightningModule):
|
|||||||
dataset = MNIST(os.getcwd(), train=True, download=True, transform=transform)
|
dataset = MNIST(os.getcwd(), train=True, download=True, transform=transform)
|
||||||
return DataLoader(dataset, batch_size=self.hparams.batch_size)
|
return DataLoader(dataset, batch_size=self.hparams.batch_size)
|
||||||
|
|
||||||
|
def on_epoch_end(self):
|
||||||
|
z = torch.randn(8, self.hparams.latent_dim)
|
||||||
|
# match gpu device (or keep as cpu)
|
||||||
|
if self.on_gpu:
|
||||||
|
z = z.cuda(self.last_imgs.device.index)
|
||||||
|
|
||||||
|
# log sampled images
|
||||||
|
sample_imgs = self.forward(z)
|
||||||
|
grid = torchvision.utils.make_grid(sample_imgs)
|
||||||
|
self.logger.experiment.add_image(f'generated_images', grid, self.current_epoch)
|
||||||
|
|
||||||
|
|
||||||
def main(hparams):
|
def main(hparams):
|
||||||
# save tensorboard logs
|
# ------------------------
|
||||||
exp = Experiment(save_dir=os.getcwd())
|
# 1 INIT LIGHTNING MODEL
|
||||||
|
# ------------------------
|
||||||
# init model
|
|
||||||
model = GAN(hparams)
|
model = GAN(hparams)
|
||||||
|
|
||||||
# fit trainer on CPU
|
# ------------------------
|
||||||
trainer = pl.Trainer(experiment=exp, max_nb_epochs=200)
|
# 2 INIT TRAINER
|
||||||
|
# ------------------------
|
||||||
|
trainer = pl.Trainer()
|
||||||
|
|
||||||
|
# ------------------------
|
||||||
|
# 3 START TRAINING
|
||||||
|
# ------------------------
|
||||||
trainer.fit(model)
|
trainer.fit(model)
|
||||||
|
|
||||||
|
|
||||||
@@ -169,9 +196,12 @@ if __name__ == '__main__':
|
|||||||
parser = ArgumentParser()
|
parser = ArgumentParser()
|
||||||
parser.add_argument("--batch_size", type=int, default=64, help="size of the batches")
|
parser.add_argument("--batch_size", type=int, default=64, help="size of the batches")
|
||||||
parser.add_argument("--lr", type=float, default=0.0002, help="adam: learning rate")
|
parser.add_argument("--lr", type=float, default=0.0002, help="adam: learning rate")
|
||||||
parser.add_argument("--b1", type=float, default=0.5, help="adam: decay of first order momentum of gradient")
|
parser.add_argument("--b1", type=float, default=0.5,
|
||||||
parser.add_argument("--b2", type=float, default=0.999, help="adam: decay of first order momentum of gradient")
|
help="adam: decay of first order momentum of gradient")
|
||||||
parser.add_argument("--latent_dim", type=int, default=100, help="dimensionality of the latent space")
|
parser.add_argument("--b2", type=float, default=0.999,
|
||||||
|
help="adam: decay of first order momentum of gradient")
|
||||||
|
parser.add_argument("--latent_dim", type=int, default=100,
|
||||||
|
help="dimensionality of the latent space")
|
||||||
|
|
||||||
hparams = parser.parse_args()
|
hparams = parser.parse_args()
|
||||||
|
|
||||||
+3
-3
@@ -2,12 +2,13 @@
|
|||||||
Multi-node example (GPU)
|
Multi-node example (GPU)
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
from argparse import ArgumentParser
|
from pl_examples.basic_examples.lightning_module_template import LightningTemplateModel
|
||||||
from pytorch_lightning import Trainer
|
from pytorch_lightning import Trainer
|
||||||
from examples.basic_examples.lightning_module_template import LightningTemplateModel
|
|
||||||
|
|
||||||
SEED = 2334
|
SEED = 2334
|
||||||
torch.manual_seed(SEED)
|
torch.manual_seed(SEED)
|
||||||
@@ -41,7 +42,6 @@ def main(hparams):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
||||||
root_dir = os.path.dirname(os.path.realpath(__file__))
|
root_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
parent_parser = ArgumentParser(add_help=False)
|
parent_parser = ArgumentParser(add_help=False)
|
||||||
|
|
||||||
+3
-3
@@ -2,12 +2,13 @@
|
|||||||
Multi-node example (GPU)
|
Multi-node example (GPU)
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
from argparse import ArgumentParser
|
from pl_examples.basic_examples.lightning_module_template import LightningTemplateModel
|
||||||
from pytorch_lightning import Trainer
|
from pytorch_lightning import Trainer
|
||||||
from examples.basic_examples.lightning_module_template import LightningTemplateModel
|
|
||||||
|
|
||||||
SEED = 2334
|
SEED = 2334
|
||||||
torch.manual_seed(SEED)
|
torch.manual_seed(SEED)
|
||||||
@@ -41,7 +42,6 @@ def main(hparams):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
||||||
root_dir = os.path.dirname(os.path.realpath(__file__))
|
root_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
parent_parser = ArgumentParser(add_help=False)
|
parent_parser = ArgumentParser(add_help=False)
|
||||||
|
|
||||||
@@ -1,9 +1,36 @@
|
|||||||
from .trainer.trainer import Trainer
|
"""Package info"""
|
||||||
from .root_module.root_module import LightningModule
|
|
||||||
from .root_module.decorators import data_loader
|
|
||||||
|
|
||||||
__all__ = [
|
__version__ = '0.5.3'
|
||||||
'Trainer',
|
__author__ = ' William Falcon et al.'
|
||||||
'LightningModule',
|
__author_email__ = 'waf2107@columbia.edu'
|
||||||
'data_loader',
|
__license__ = 'Apache-2.0'
|
||||||
]
|
__homepage__ = 'https://github.com/williamFalcon/pytorch-lightning'
|
||||||
|
__docs__ = """# PyTorch Lightning
|
||||||
|
|
||||||
|
The lightweight PyTorch wrapper for ML researchers. Scale your models. Write less boilerplate.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
# This variable is injected in the __builtins__ by the build
|
||||||
|
# process. It used to enable importing subpackages of skimage when
|
||||||
|
# the binaries are not built
|
||||||
|
__LIGHTNING_SETUP__
|
||||||
|
except NameError:
|
||||||
|
__LIGHTNING_SETUP__ = False
|
||||||
|
|
||||||
|
if __LIGHTNING_SETUP__:
|
||||||
|
import sys
|
||||||
|
sys.stderr.write('Partial import of skimage during the build process.\n')
|
||||||
|
# We are not importing the rest of the scikit during the build
|
||||||
|
# process, as it may not be compiled yet
|
||||||
|
else:
|
||||||
|
from .trainer.trainer import Trainer
|
||||||
|
from .root_module.root_module import LightningModule
|
||||||
|
from .root_module.decorators import data_loader
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'Trainer',
|
||||||
|
'LightningModule',
|
||||||
|
'data_loader',
|
||||||
|
]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import logging
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from pytorch_lightning.pt_overrides.override_data_parallel import LightningDistributedDataParallel
|
from pytorch_lightning.pt_overrides.override_data_parallel import LightningDistributedDataParallel
|
||||||
@@ -92,7 +92,7 @@ class EarlyStopping(Callback):
|
|||||||
self.stopped_epoch = 0
|
self.stopped_epoch = 0
|
||||||
|
|
||||||
if mode not in ['auto', 'min', 'max']:
|
if mode not in ['auto', 'min', 'max']:
|
||||||
print('EarlyStopping mode %s is unknown, fallback to auto mode.' % mode)
|
logging.info(f'EarlyStopping mode {mode} is unknown, fallback to auto mode.')
|
||||||
mode = 'auto'
|
mode = 'auto'
|
||||||
|
|
||||||
if mode == 'min':
|
if mode == 'min':
|
||||||
@@ -122,10 +122,12 @@ class EarlyStopping(Callback):
|
|||||||
current = logs.get(self.monitor)
|
current = logs.get(self.monitor)
|
||||||
stop_training = False
|
stop_training = False
|
||||||
if current is None:
|
if current is None:
|
||||||
print('Early stopping conditioned on metric `%s` '
|
warnings.warn(
|
||||||
'which is not available. Available metrics are: %s' %
|
f'Early stopping conditioned on metric `{self.monitor}`'
|
||||||
(self.monitor, ','.join(list(logs.keys()))), RuntimeWarning)
|
f' which is not available. Available metrics are: {",".join(list(logs.keys()))}',
|
||||||
exit(-1)
|
RuntimeWarning)
|
||||||
|
stop_training = True
|
||||||
|
return stop_training
|
||||||
|
|
||||||
if self.monitor_op(current - self.min_delta, self.best):
|
if self.monitor_op(current - self.min_delta, self.best):
|
||||||
self.best = current
|
self.best = current
|
||||||
@@ -141,7 +143,7 @@ class EarlyStopping(Callback):
|
|||||||
|
|
||||||
def on_train_end(self, logs=None):
|
def on_train_end(self, logs=None):
|
||||||
if self.stopped_epoch > 0 and self.verbose > 0:
|
if self.stopped_epoch > 0 and self.verbose > 0:
|
||||||
print('Epoch %05d: early stopping' % (self.stopped_epoch + 1))
|
logging.info(f'Epoch {self.stopped_epoch + 1:05d}: early stopping')
|
||||||
|
|
||||||
|
|
||||||
class ModelCheckpoint(Callback):
|
class ModelCheckpoint(Callback):
|
||||||
@@ -174,9 +176,19 @@ class ModelCheckpoint(Callback):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, filepath, monitor='val_loss', verbose=0,
|
def __init__(self, filepath, monitor='val_loss', verbose=0,
|
||||||
save_best_only=False, save_weights_only=False,
|
save_best_only=True, save_weights_only=False,
|
||||||
mode='auto', period=1, prefix=''):
|
mode='auto', period=1, prefix=''):
|
||||||
super(ModelCheckpoint, self).__init__()
|
super(ModelCheckpoint, self).__init__()
|
||||||
|
if (
|
||||||
|
save_best_only and
|
||||||
|
os.path.isdir(filepath) and
|
||||||
|
len(os.listdir(filepath)) > 0
|
||||||
|
):
|
||||||
|
warnings.warn(
|
||||||
|
f"Checkpoint directory {filepath} exists and is not empty with save_best_only=True."
|
||||||
|
"All files in this directory will be deleted when a checkpoint is saved!"
|
||||||
|
)
|
||||||
|
|
||||||
self.monitor = monitor
|
self.monitor = monitor
|
||||||
self.verbose = verbose
|
self.verbose = verbose
|
||||||
self.filepath = filepath
|
self.filepath = filepath
|
||||||
@@ -187,8 +199,9 @@ class ModelCheckpoint(Callback):
|
|||||||
self.prefix = prefix
|
self.prefix = prefix
|
||||||
|
|
||||||
if mode not in ['auto', 'min', 'max']:
|
if mode not in ['auto', 'min', 'max']:
|
||||||
print('ModelCheckpoint mode %s is unknown, '
|
warnings.warn(
|
||||||
'fallback to auto mode.' % (mode), RuntimeWarning)
|
f'ModelCheckpoint mode {mode} is unknown, '
|
||||||
|
'fallback to auto mode.', RuntimeWarning)
|
||||||
mode = 'auto'
|
mode = 'auto'
|
||||||
|
|
||||||
if mode == 'min':
|
if mode == 'min':
|
||||||
@@ -232,25 +245,26 @@ class ModelCheckpoint(Callback):
|
|||||||
if self.save_best_only:
|
if self.save_best_only:
|
||||||
current = logs.get(self.monitor)
|
current = logs.get(self.monitor)
|
||||||
if current is None:
|
if current is None:
|
||||||
print('Can save best model only with %s available,'
|
warnings.warn(
|
||||||
' skipping.' % (self.monitor), RuntimeWarning)
|
f'Can save best model only with {self.monitor} available,'
|
||||||
|
' skipping.', RuntimeWarning)
|
||||||
else:
|
else:
|
||||||
if self.monitor_op(current, self.best):
|
if self.monitor_op(current, self.best):
|
||||||
if self.verbose > 0:
|
if self.verbose > 0:
|
||||||
print('\nEpoch %05d: %s improved from %0.5f to %0.5f,'
|
logging.info(
|
||||||
' saving model to %s'
|
f'\nEpoch {epoch + 1:05d}: {self.monitor} improved'
|
||||||
% (epoch + 1, self.monitor, self.best,
|
f' from {self.best:0.5f} to {current:0.5f},',
|
||||||
current, filepath))
|
f' saving model to {filepath}')
|
||||||
self.best = current
|
self.best = current
|
||||||
self.save_model(filepath, overwrite=True)
|
self.save_model(filepath, overwrite=True)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if self.verbose > 0:
|
if self.verbose > 0:
|
||||||
print('\nEpoch %05d: %s did not improve' %
|
logging.info(
|
||||||
(epoch + 1, self.monitor))
|
f'\nEpoch {epoch + 1:05d}: {self.monitor} did not improve')
|
||||||
else:
|
else:
|
||||||
if self.verbose > 0:
|
if self.verbose > 0:
|
||||||
print('\nEpoch %05d: saving model to %s' % (epoch + 1, filepath))
|
logging.info(f'\nEpoch {epoch + 1:05d}: saving model to {filepath}')
|
||||||
self.save_model(filepath, overwrite=False)
|
self.save_model(filepath, overwrite=False)
|
||||||
|
|
||||||
|
|
||||||
@@ -259,6 +273,7 @@ class GradientAccumulationScheduler(Callback):
|
|||||||
# Arguments
|
# Arguments
|
||||||
scheduling: dict, scheduling in format {epoch: accumulation_factor}
|
scheduling: dict, scheduling in format {epoch: accumulation_factor}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, scheduling: dict):
|
def __init__(self, scheduling: dict):
|
||||||
if scheduling == {}: # empty dict error
|
if scheduling == {}: # empty dict error
|
||||||
raise TypeError("Empty dict cannot be interpreted correct")
|
raise TypeError("Empty dict cannot be interpreted correct")
|
||||||
@@ -290,6 +305,6 @@ if __name__ == '__main__':
|
|||||||
losses = [10, 9, 8, 8, 6, 4.3, 5, 4.4, 2.8, 2.5]
|
losses = [10, 9, 8, 8, 6, 4.3, 5, 4.4, 2.8, 2.5]
|
||||||
for i, loss in enumerate(losses):
|
for i, loss in enumerate(losses):
|
||||||
should_stop = c.on_epoch_end(i, logs={'val_loss': loss})
|
should_stop = c.on_epoch_end(i, logs={'val_loss': loss})
|
||||||
print(loss)
|
logging.info(loss)
|
||||||
if should_stop:
|
if should_stop:
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
|
from os import environ
|
||||||
from .base import LightningLoggerBase, rank_zero_only
|
from .base import LightningLoggerBase, rank_zero_only
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from .test_tube_logger import TestTubeLogger
|
from .test_tube_logger import TestTubeLogger
|
||||||
except ModuleNotFoundError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
from .mlflow_logger import MLFlowLogger
|
from .mlflow_logger import MLFlowLogger
|
||||||
except ModuleNotFoundError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
try:
|
||||||
|
# needed to prevent ImportError and duplicated logs.
|
||||||
|
environ["COMET_DISABLE_AUTO_LOGGING"] = "1"
|
||||||
|
|
||||||
|
from .comet_logger import CometLogger
|
||||||
|
except ImportError:
|
||||||
|
del environ["COMET_DISABLE_AUTO_LOGGING"]
|
||||||
|
|||||||
@@ -65,7 +65,12 @@ class LightningLoggerBase(object):
|
|||||||
"""Set the process rank"""
|
"""Set the process rank"""
|
||||||
self._rank = value
|
self._rank = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the experiment name"""
|
||||||
|
raise NotImplementedError("Sub-classes must provide a name property")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def version(self):
|
def version(self):
|
||||||
"""Return the experiment version"""
|
"""Return the experiment version"""
|
||||||
return None
|
raise NotImplementedError("Sub-classes must provide a version property")
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
try:
|
||||||
|
from comet_ml import Experiment as CometExperiment
|
||||||
|
except ImportError:
|
||||||
|
raise ImportError('Missing comet_ml package.')
|
||||||
|
|
||||||
|
from .base import LightningLoggerBase, rank_zero_only
|
||||||
|
|
||||||
|
|
||||||
|
class CometLogger(LightningLoggerBase):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(CometLogger, self).__init__()
|
||||||
|
self.experiment = CometExperiment(*args, **kwargs)
|
||||||
|
|
||||||
|
@rank_zero_only
|
||||||
|
def log_hyperparams(self, params):
|
||||||
|
self.experiment.log_parameters(vars(params))
|
||||||
|
|
||||||
|
@rank_zero_only
|
||||||
|
def log_metrics(self, metrics, step_num):
|
||||||
|
# self.experiment.set_epoch(self, metrics.get('epoch', 0))
|
||||||
|
self.experiment.log_metrics(metrics)
|
||||||
|
|
||||||
|
@rank_zero_only
|
||||||
|
def finalize(self, status):
|
||||||
|
self.experiment.end()
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
from time import time
|
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
from time import time
|
||||||
|
|
||||||
import mlflow
|
try:
|
||||||
|
import mlflow
|
||||||
|
except ImportError:
|
||||||
|
raise ImportError('Missing mlflow package.')
|
||||||
|
|
||||||
from .base import LightningLoggerBase, rank_zero_only
|
from .base import LightningLoggerBase, rank_zero_only
|
||||||
|
|
||||||
@@ -9,33 +12,34 @@ logger = getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class MLFlowLogger(LightningLoggerBase):
|
class MLFlowLogger(LightningLoggerBase):
|
||||||
def __init__(self, experiment_name, tracking_uri=None):
|
def __init__(self, experiment_name, tracking_uri=None, tags=None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.client = mlflow.tracking.MlflowClient(tracking_uri)
|
self.experiment = mlflow.tracking.MlflowClient(tracking_uri)
|
||||||
self.experiment_name = experiment_name
|
self.experiment_name = experiment_name
|
||||||
self._run_id = None
|
self._run_id = None
|
||||||
|
self.tags = tags
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def run_id(self):
|
def run_id(self):
|
||||||
if self._run_id is not None:
|
if self._run_id is not None:
|
||||||
return self._run_id
|
return self._run_id
|
||||||
|
|
||||||
experiment = self.client.get_experiment_by_name(self.experiment_name)
|
experiment = self.experiment.get_experiment_by_name(self.experiment_name)
|
||||||
if experiment is None:
|
if experiment is None:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"Experiment with name f{self.experiment_name} not found. Creating it."
|
f"Experiment with name f{self.experiment_name} not found. Creating it."
|
||||||
)
|
)
|
||||||
self.client.create_experiment(self.experiment_name)
|
self.experiment.create_experiment(self.experiment_name)
|
||||||
experiment = self.client.get_experiment_by_name(self.experiment_name)
|
experiment = self.experiment.get_experiment_by_name(self.experiment_name)
|
||||||
|
|
||||||
run = self.client.create_run(experiment.experiment_id)
|
run = self.experiment.create_run(experiment.experiment_id, tags=self.tags)
|
||||||
self._run_id = run.info.run_id
|
self._run_id = run.info.run_id
|
||||||
return self._run_id
|
return self._run_id
|
||||||
|
|
||||||
@rank_zero_only
|
@rank_zero_only
|
||||||
def log_hyperparams(self, params):
|
def log_hyperparams(self, params):
|
||||||
for k, v in vars(params).items():
|
for k, v in vars(params).items():
|
||||||
self.client.log_param(self.run_id, k, v)
|
self.experiment.log_param(self.run_id, k, v)
|
||||||
|
|
||||||
@rank_zero_only
|
@rank_zero_only
|
||||||
def log_metrics(self, metrics, step_num=None):
|
def log_metrics(self, metrics, step_num=None):
|
||||||
@@ -46,11 +50,21 @@ class MLFlowLogger(LightningLoggerBase):
|
|||||||
f"Discarding metric with string value {k}={v}"
|
f"Discarding metric with string value {k}={v}"
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
self.client.log_metric(self.run_id, k, v, timestamp_ms, step_num)
|
self.experiment.log_metric(self.run_id, k, v, timestamp_ms, step_num)
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@rank_zero_only
|
@rank_zero_only
|
||||||
def finalize(self, status="FINISHED"):
|
def finalize(self, status="FINISHED"):
|
||||||
self.client.set_terminated(self.run_id, status)
|
if status == 'success':
|
||||||
|
status = 'FINISHED'
|
||||||
|
self.experiment.set_terminated(self.run_id, status)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self.experiment_name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def version(self):
|
||||||
|
return self._run_id
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
import os.path
|
try:
|
||||||
from copy import copy
|
from test_tube import Experiment
|
||||||
|
except ImportError:
|
||||||
|
raise ImportError('Missing test-tube package.')
|
||||||
|
|
||||||
from .base import LightningLoggerBase, rank_zero_only
|
from .base import LightningLoggerBase, rank_zero_only
|
||||||
|
|
||||||
from test_tube import Experiment
|
|
||||||
|
|
||||||
|
|
||||||
class TestTubeLogger(LightningLoggerBase):
|
class TestTubeLogger(LightningLoggerBase):
|
||||||
__test__ = False
|
__test__ = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, save_dir, name="default", description=None, debug=False,
|
self, save_dir, name="default", description=None, debug=False,
|
||||||
version=None, create_git_tag=False
|
version=None, create_git_tag=False
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.save_dir = save_dir
|
self.save_dir = save_dir
|
||||||
self.name = name
|
self._name = name
|
||||||
self.description = description
|
self.description = description
|
||||||
self.debug = debug
|
self.debug = debug
|
||||||
self._version = version
|
self._version = version
|
||||||
@@ -26,9 +26,10 @@ class TestTubeLogger(LightningLoggerBase):
|
|||||||
def experiment(self):
|
def experiment(self):
|
||||||
if self._experiment is not None:
|
if self._experiment is not None:
|
||||||
return self._experiment
|
return self._experiment
|
||||||
|
|
||||||
self._experiment = Experiment(
|
self._experiment = Experiment(
|
||||||
save_dir=self.save_dir,
|
save_dir=self.save_dir,
|
||||||
name=self.name,
|
name=self._name,
|
||||||
debug=self.debug,
|
debug=self.debug,
|
||||||
version=self.version,
|
version=self.version,
|
||||||
description=self.description,
|
description=self.description,
|
||||||
@@ -39,37 +40,52 @@ class TestTubeLogger(LightningLoggerBase):
|
|||||||
|
|
||||||
@rank_zero_only
|
@rank_zero_only
|
||||||
def log_hyperparams(self, params):
|
def log_hyperparams(self, params):
|
||||||
|
# TODO: HACK figure out where this is being set to true
|
||||||
|
self.experiment.debug = self.debug
|
||||||
self.experiment.argparse(params)
|
self.experiment.argparse(params)
|
||||||
|
|
||||||
@rank_zero_only
|
@rank_zero_only
|
||||||
def log_metrics(self, metrics, step_num=None):
|
def log_metrics(self, metrics, step_num=None):
|
||||||
|
# TODO: HACK figure out where this is being set to true
|
||||||
|
self.experiment.debug = self.debug
|
||||||
self.experiment.log(metrics, global_step=step_num)
|
self.experiment.log(metrics, global_step=step_num)
|
||||||
|
|
||||||
@rank_zero_only
|
@rank_zero_only
|
||||||
def save(self):
|
def save(self):
|
||||||
|
# TODO: HACK figure out where this is being set to true
|
||||||
|
self.experiment.debug = self.debug
|
||||||
self.experiment.save()
|
self.experiment.save()
|
||||||
|
|
||||||
@rank_zero_only
|
@rank_zero_only
|
||||||
def finalize(self, status):
|
def finalize(self, status):
|
||||||
|
# TODO: HACK figure out where this is being set to true
|
||||||
|
self.experiment.debug = self.debug
|
||||||
self.save()
|
self.save()
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
|
@rank_zero_only
|
||||||
def close(self):
|
def close(self):
|
||||||
self.experiment.close()
|
# TODO: HACK figure out where this is being set to true
|
||||||
|
self.experiment.debug = self.debug
|
||||||
|
exp = self.experiment
|
||||||
|
exp.close()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def rank(self):
|
def rank(self):
|
||||||
if self._experiment is None:
|
return self._rank
|
||||||
return self._rank
|
|
||||||
else:
|
|
||||||
return self.experiment.rank
|
|
||||||
|
|
||||||
@rank.setter
|
@rank.setter
|
||||||
def rank(self, value):
|
def rank(self, value):
|
||||||
|
self._rank = value
|
||||||
|
if self._experiment is not None:
|
||||||
|
self.experiment.rank = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
if self._experiment is None:
|
if self._experiment is None:
|
||||||
self._rank = value
|
return self._name
|
||||||
else:
|
else:
|
||||||
return self.experiment.rank
|
return self.experiment.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def version(self):
|
def version(self):
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
from torch.nn import DataParallel
|
|
||||||
from torch.nn.parallel import DistributedDataParallel
|
|
||||||
import itertools
|
import itertools
|
||||||
|
import threading
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
||||||
import threading
|
|
||||||
import torch
|
import torch
|
||||||
from torch.cuda._utils import _get_device_index
|
from torch.cuda._utils import _get_device_index
|
||||||
|
from torch.nn import DataParallel
|
||||||
|
from torch.nn.parallel import DistributedDataParallel
|
||||||
|
|
||||||
|
|
||||||
def _find_tensors(obj): # pragma: no cover
|
def _find_tensors(obj): # pragma: no cover
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ def data_loader(fn):
|
|||||||
try:
|
try:
|
||||||
value = fn(self) # Lazy evaluation, done only once.
|
value = fn(self) # Lazy evaluation, done only once.
|
||||||
if (
|
if (
|
||||||
value is not None and
|
value is not None and
|
||||||
not isinstance(value, list) and
|
not isinstance(value, list) and
|
||||||
fn.__name__ in['test_dataloader', 'val_dataloader']
|
fn.__name__ in ['test_dataloader', 'val_dataloader']
|
||||||
):
|
):
|
||||||
value = [value]
|
value = [value]
|
||||||
except AttributeError as e:
|
except AttributeError as e:
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
import torch
|
import torch
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from apex import amp
|
||||||
|
|
||||||
|
APEX_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
APEX_AVAILABLE = False
|
||||||
|
|
||||||
|
|
||||||
class ModelHooks(torch.nn.Module):
|
class ModelHooks(torch.nn.Module):
|
||||||
|
|
||||||
def on_sanity_check_start(self):
|
def on_sanity_check_start(self):
|
||||||
@@ -48,3 +56,17 @@ class ModelHooks(torch.nn.Module):
|
|||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def backward(self, use_amp, loss, optimizer):
|
||||||
|
"""
|
||||||
|
Override backward with your own implementation if you need to
|
||||||
|
:param use_amp: Whether amp was requested or not
|
||||||
|
:param loss: Loss is already scaled by accumulated grads
|
||||||
|
:param optimizer: Current optimizer being used
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if use_amp:
|
||||||
|
with amp.scale_loss(loss, optimizer) as scaled_loss:
|
||||||
|
scaled_loss.backward()
|
||||||
|
else:
|
||||||
|
loss.backward()
|
||||||
|
|||||||
@@ -3,20 +3,23 @@ Generates a summary of a model's layers and dimensionality
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
import gc
|
import gc
|
||||||
|
import os
|
||||||
import torch
|
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
import torch
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
class ModelSummary(object):
|
class ModelSummary(object):
|
||||||
|
|
||||||
def __init__(self, model):
|
def __init__(self, model, mode='full'):
|
||||||
'''
|
'''
|
||||||
Generates summaries of model layers and dimensions.
|
Generates summaries of model layers and dimensions.
|
||||||
'''
|
'''
|
||||||
self.model = model
|
self.model = model
|
||||||
|
self.mode = mode
|
||||||
self.in_sizes = []
|
self.in_sizes = []
|
||||||
self.out_sizes = []
|
self.out_sizes = []
|
||||||
|
|
||||||
@@ -28,9 +31,20 @@ class ModelSummary(object):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return self.summary.__str__()
|
return self.summary.__str__()
|
||||||
|
|
||||||
|
def named_modules(self):
|
||||||
|
if self.mode == 'full':
|
||||||
|
mods = self.model.named_modules()
|
||||||
|
mods = list(mods)[1:] # do not include root module (LightningModule)
|
||||||
|
elif self.mode == 'top':
|
||||||
|
# the children are the top-level modules
|
||||||
|
mods = self.model.named_children()
|
||||||
|
else:
|
||||||
|
mods = []
|
||||||
|
return list(mods)
|
||||||
|
|
||||||
def get_variable_sizes(self):
|
def get_variable_sizes(self):
|
||||||
'''Run sample input through each layer to get output sizes'''
|
'''Run sample input through each layer to get output sizes'''
|
||||||
mods = list(self.model.modules())
|
mods = self.named_modules()
|
||||||
in_sizes = []
|
in_sizes = []
|
||||||
out_sizes = []
|
out_sizes = []
|
||||||
input_ = self.model.example_input_array
|
input_ = self.model.example_input_array
|
||||||
@@ -43,8 +57,7 @@ class ModelSummary(object):
|
|||||||
|
|
||||||
with torch.no_grad():
|
with torch.no_grad():
|
||||||
|
|
||||||
for i in range(1, len(mods)):
|
for _, m in mods:
|
||||||
m = mods[i]
|
|
||||||
if type(input_) is list or type(input_) is tuple: # pragma: no cover
|
if type(input_) is list or type(input_) is tuple: # pragma: no cover
|
||||||
out = m(*input_)
|
out = m(*input_)
|
||||||
else:
|
else:
|
||||||
@@ -72,16 +85,17 @@ class ModelSummary(object):
|
|||||||
|
|
||||||
self.in_sizes = in_sizes
|
self.in_sizes = in_sizes
|
||||||
self.out_sizes = out_sizes
|
self.out_sizes = out_sizes
|
||||||
|
assert len(in_sizes) == len(out_sizes)
|
||||||
return
|
return
|
||||||
|
|
||||||
def get_layer_names(self):
|
def get_layer_names(self):
|
||||||
'''Collect Layer Names'''
|
'''Collect Layer Names'''
|
||||||
mods = list(self.model.named_modules())
|
mods = self.named_modules()
|
||||||
names = []
|
names = []
|
||||||
layers = []
|
layers = []
|
||||||
for m in mods[1:]:
|
for name, m in mods:
|
||||||
names += [m[0]]
|
names += [name]
|
||||||
layers += [str(m[1].__class__)]
|
layers += [str(m.__class__)]
|
||||||
|
|
||||||
layer_types = [x.split('.')[-1][:-2] for x in layers]
|
layer_types = [x.split('.')[-1][:-2] for x in layers]
|
||||||
|
|
||||||
@@ -91,11 +105,9 @@ class ModelSummary(object):
|
|||||||
|
|
||||||
def get_parameter_sizes(self):
|
def get_parameter_sizes(self):
|
||||||
'''Get sizes of all parameters in `model`'''
|
'''Get sizes of all parameters in `model`'''
|
||||||
mods = list(self.model.modules())
|
mods = self.named_modules()
|
||||||
sizes = []
|
sizes = []
|
||||||
|
for _, m in mods:
|
||||||
for i in range(1, len(mods)):
|
|
||||||
m = mods[i]
|
|
||||||
p = list(m.parameters())
|
p = list(m.parameters())
|
||||||
modsz = []
|
modsz = []
|
||||||
for j in range(len(p)):
|
for j in range(len(p)):
|
||||||
@@ -133,9 +145,9 @@ class ModelSummary(object):
|
|||||||
df['Name'] = self.layer_names
|
df['Name'] = self.layer_names
|
||||||
df['Type'] = self.layer_types
|
df['Type'] = self.layer_types
|
||||||
df['Params'] = self.param_nums
|
df['Params'] = self.param_nums
|
||||||
|
df['Params'] = df['Params'].map(get_human_readable_count)
|
||||||
|
|
||||||
if self.model.example_input_array is not None:
|
if self.model.example_input_array is not None:
|
||||||
|
|
||||||
df['In_sizes'] = self.in_sizes
|
df['In_sizes'] = self.in_sizes
|
||||||
df['Out_sizes'] = self.out_sizes
|
df['Out_sizes'] = self.out_sizes
|
||||||
|
|
||||||
@@ -156,7 +168,7 @@ def print_mem_stack(): # pragma: no cover
|
|||||||
for obj in gc.get_objects():
|
for obj in gc.get_objects():
|
||||||
try:
|
try:
|
||||||
if torch.is_tensor(obj) or (hasattr(obj, 'data') and torch.is_tensor(obj.data)):
|
if torch.is_tensor(obj) or (hasattr(obj, 'data') and torch.is_tensor(obj.data)):
|
||||||
print(type(obj), obj.size())
|
logging.info(type(obj), obj.size())
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -188,19 +200,10 @@ def get_memory_profile(mode):
|
|||||||
memory_map = get_gpu_memory_map()
|
memory_map = get_gpu_memory_map()
|
||||||
|
|
||||||
if mode == 'min_max':
|
if mode == 'min_max':
|
||||||
min_mem = 1000000
|
min_index, min_memory = min(memory_map.items(), key=lambda item: item[1])
|
||||||
min_k = None
|
max_index, max_memory = max(memory_map.items(), key=lambda item: item[1])
|
||||||
max_mem = 0
|
|
||||||
max_k = None
|
|
||||||
for k, v in memory_map:
|
|
||||||
if v > max_mem:
|
|
||||||
max_mem = v
|
|
||||||
max_k = k
|
|
||||||
if v < min_mem:
|
|
||||||
min_mem = v
|
|
||||||
min_k = k
|
|
||||||
|
|
||||||
memory_map = {min_k: min_mem, max_k: max_mem}
|
memory_map = {min_index: min_memory, max_index: max_memory}
|
||||||
|
|
||||||
return memory_map
|
return memory_map
|
||||||
|
|
||||||
@@ -214,15 +217,41 @@ def get_gpu_memory_map():
|
|||||||
Keys are device ids as integers.
|
Keys are device ids as integers.
|
||||||
Values are memory usage as integers in MB.
|
Values are memory usage as integers in MB.
|
||||||
"""
|
"""
|
||||||
result = subprocess.check_output(
|
result = subprocess.run(
|
||||||
[
|
[
|
||||||
'nvidia-smi', '--query-gpu=memory.used',
|
'nvidia-smi',
|
||||||
'--format=csv,nounits,noheader'
|
'--query-gpu=memory.used',
|
||||||
], encoding='utf-8')
|
'--format=csv,nounits,noheader',
|
||||||
|
],
|
||||||
|
encoding='utf-8',
|
||||||
|
capture_output=True,
|
||||||
|
check=True)
|
||||||
# Convert lines into a dictionary
|
# Convert lines into a dictionary
|
||||||
gpu_memory = [int(x) for x in result.strip().split('\n')]
|
gpu_memory = [int(x) for x in result.stdout.strip().split(os.linesep)]
|
||||||
gpu_memory_map = {}
|
gpu_memory_map = {f'gpu_{index}': memory for index, memory in enumerate(gpu_memory)}
|
||||||
for k, v in zip(range(len(gpu_memory)), gpu_memory):
|
|
||||||
k = f'gpu_{k}'
|
|
||||||
gpu_memory_map[k] = v
|
|
||||||
return gpu_memory_map
|
return gpu_memory_map
|
||||||
|
|
||||||
|
|
||||||
|
def get_human_readable_count(number):
|
||||||
|
"""
|
||||||
|
Abbreviates an integer number with K, M, B, T for thousands, millions,
|
||||||
|
billions and trillions, respectively.
|
||||||
|
Examples:
|
||||||
|
123 -> 123
|
||||||
|
1234 -> 1 K (one thousand)
|
||||||
|
2e6 -> 2 M (two million)
|
||||||
|
3e9 -> 3 B (three billion)
|
||||||
|
4e12 -> 4 T (four trillion)
|
||||||
|
5e15 -> 5,000 T
|
||||||
|
:param number: a positive integer number
|
||||||
|
:returns a string formatted according to the pattern described above.
|
||||||
|
"""
|
||||||
|
assert number >= 0
|
||||||
|
labels = [' ', 'K', 'M', 'B', 'T']
|
||||||
|
num_digits = int(np.floor(np.log10(number)) + 1 if number > 0 else 1)
|
||||||
|
num_groups = int(np.ceil(num_digits / 3))
|
||||||
|
num_groups = min(num_groups, len(labels)) # don't abbreviate beyond trillions
|
||||||
|
shift = -3 * (num_groups - 1)
|
||||||
|
number = number * (10 ** shift)
|
||||||
|
index = num_groups - 1
|
||||||
|
return f'{int(number):,d} {labels[index]}'
|
||||||
|
|||||||
@@ -1,11 +1,19 @@
|
|||||||
|
import os
|
||||||
|
import warnings
|
||||||
|
import collections
|
||||||
|
from argparse import Namespace
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
import torch.distributed as dist
|
||||||
|
|
||||||
from pytorch_lightning.root_module.memory import ModelSummary
|
|
||||||
from pytorch_lightning.root_module.grads import GradInformation
|
|
||||||
from pytorch_lightning.trainer.trainer_io import load_hparams_from_tags_csv
|
|
||||||
from pytorch_lightning.root_module.model_saving import ModelIO
|
|
||||||
from pytorch_lightning.root_module.hooks import ModelHooks
|
|
||||||
from pytorch_lightning.root_module.decorators import data_loader
|
from pytorch_lightning.root_module.decorators import data_loader
|
||||||
|
from pytorch_lightning.root_module.grads import GradInformation
|
||||||
|
from pytorch_lightning.root_module.hooks import ModelHooks
|
||||||
|
from pytorch_lightning.root_module.memory import ModelSummary
|
||||||
|
from pytorch_lightning.root_module.model_saving import ModelIO
|
||||||
|
from pytorch_lightning.trainer.trainer_io import load_hparams_from_tags_csv
|
||||||
|
import logging
|
||||||
|
from pytorch_lightning.pt_overrides.override_data_parallel import LightningDistributedDataParallel
|
||||||
|
|
||||||
|
|
||||||
class LightningModule(GradInformation, ModelIO, ModelHooks):
|
class LightningModule(GradInformation, ModelIO, ModelHooks):
|
||||||
@@ -43,10 +51,19 @@ class LightningModule(GradInformation, ModelIO, ModelHooks):
|
|||||||
return loss, dict with metrics for tqdm
|
return loss, dict with metrics for tqdm
|
||||||
:param called with batch, batch_nb
|
:param called with batch, batch_nb
|
||||||
additional: optimizer_i if multiple optimizers used
|
additional: optimizer_i if multiple optimizers used
|
||||||
:return:
|
:return: dict with loss key and optional log, progress keys
|
||||||
|
if implementing training_step, return whatever you need in that step
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def training_end(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
return loss, dict with metrics for tqdm
|
||||||
|
:param called with outputs of training_step
|
||||||
|
:return: dict with loss key and optional log, progress keys
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
def validation_step(self, *args, **kwargs):
|
def validation_step(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
return whatever outputs will need to be aggregated in validation_end
|
return whatever outputs will need to be aggregated in validation_end
|
||||||
@@ -85,6 +102,72 @@ class LightningModule(GradInformation, ModelIO, ModelHooks):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def configure_ddp(self, model, device_ids):
|
||||||
|
"""
|
||||||
|
Override to init DDP in a different way or use your own wrapper.
|
||||||
|
Must return model.
|
||||||
|
:param model:
|
||||||
|
:param device_ids:
|
||||||
|
:return: DDP wrapped model
|
||||||
|
"""
|
||||||
|
model = LightningDistributedDataParallel(
|
||||||
|
model,
|
||||||
|
device_ids=device_ids,
|
||||||
|
find_unused_parameters=True
|
||||||
|
)
|
||||||
|
return model
|
||||||
|
|
||||||
|
def init_ddp_connection(self, proc_rank, world_size):
|
||||||
|
"""
|
||||||
|
Connect all procs in the world using the env:// init
|
||||||
|
Use the first node as the root address
|
||||||
|
"""
|
||||||
|
|
||||||
|
# use slurm job id for the port number
|
||||||
|
# guarantees unique ports across jobs from same grid search
|
||||||
|
try:
|
||||||
|
# use the last 4 numbers in the job id as the id
|
||||||
|
default_port = os.environ['SLURM_JOB_ID']
|
||||||
|
default_port = default_port[-4:]
|
||||||
|
|
||||||
|
# all ports should be in the 10k+ range
|
||||||
|
default_port = int(default_port) + 15000
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
default_port = 12910
|
||||||
|
|
||||||
|
# if user gave a port number, use that one instead
|
||||||
|
try:
|
||||||
|
default_port = os.environ['MASTER_PORT']
|
||||||
|
except Exception:
|
||||||
|
os.environ['MASTER_PORT'] = str(default_port)
|
||||||
|
|
||||||
|
# figure out the root node addr
|
||||||
|
try:
|
||||||
|
root_node = os.environ['SLURM_NODELIST'].split(' ')[0]
|
||||||
|
except Exception:
|
||||||
|
root_node = '127.0.0.2'
|
||||||
|
|
||||||
|
root_node = self.trainer.resolve_root_node_address(root_node)
|
||||||
|
os.environ['MASTER_ADDR'] = root_node
|
||||||
|
dist.init_process_group('nccl', rank=proc_rank, world_size=world_size)
|
||||||
|
|
||||||
|
def configure_apex(self, amp, model, optimizers, amp_level):
|
||||||
|
"""
|
||||||
|
Override to init AMP your own way
|
||||||
|
Must return a model and list of optimizers
|
||||||
|
:param amp:
|
||||||
|
:param model:
|
||||||
|
:param optimizers:
|
||||||
|
:param amp_level:
|
||||||
|
:return: Apex wrapped model and optimizers
|
||||||
|
"""
|
||||||
|
model, optimizers = amp.initialize(
|
||||||
|
model, optimizers, opt_level=amp_level,
|
||||||
|
)
|
||||||
|
|
||||||
|
return model, optimizers
|
||||||
|
|
||||||
def configure_optimizers(self):
|
def configure_optimizers(self):
|
||||||
"""
|
"""
|
||||||
Return a list of optimizers and a list of schedulers (could be empty)
|
Return a list of optimizers and a list of schedulers (could be empty)
|
||||||
@@ -110,13 +193,58 @@ class LightningModule(GradInformation, ModelIO, ModelHooks):
|
|||||||
# clear gradients
|
# clear gradients
|
||||||
optimizer.zero_grad()
|
optimizer.zero_grad()
|
||||||
|
|
||||||
|
def tbptt_split_batch(self, batch, split_size):
|
||||||
|
"""
|
||||||
|
Return list of batch splits. Each split will be passed to forward_step to enable truncated
|
||||||
|
back propagation through time. The default implementation splits root level Tensors and
|
||||||
|
Sequences at dim=1 (i.e. time dim). It assumes that each time dim is the same length.
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
time_dims = [len(x[0]) for x in batch if isinstance(
|
||||||
|
x, torch.Tensor) or isinstance(x, collections.Sequence)]
|
||||||
|
assert len(time_dims) >= 1, "Unable to determine batch time dimension"
|
||||||
|
assert all(x == time_dims[0] for x in time_dims), "Batch time dimension length is ambiguous"
|
||||||
|
|
||||||
|
splits = []
|
||||||
|
for t in range(0, time_dims[0], split_size):
|
||||||
|
batch_split = []
|
||||||
|
for i, x in enumerate(batch):
|
||||||
|
if isinstance(x, torch.Tensor):
|
||||||
|
split_x = x[:, t:t + split_size]
|
||||||
|
elif isinstance(x, collections.Sequence):
|
||||||
|
split_x = [None] * len(x)
|
||||||
|
for batch_idx in range(len(x)):
|
||||||
|
split_x[batch_idx] = x[batch_idx][t:t + split_size]
|
||||||
|
|
||||||
|
batch_split.append(split_x)
|
||||||
|
|
||||||
|
splits.append(batch_split)
|
||||||
|
|
||||||
|
return splits
|
||||||
|
|
||||||
|
@data_loader
|
||||||
|
def tng_dataloader(self):
|
||||||
|
"""
|
||||||
|
Implement a PyTorch DataLoader
|
||||||
|
* Deprecated in v0.5.0. use train_dataloader instead. *
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
@data_loader
|
@data_loader
|
||||||
def train_dataloader(self):
|
def train_dataloader(self):
|
||||||
"""
|
"""
|
||||||
Implement a PyTorch DataLoader
|
Implement a PyTorch DataLoader
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
#
|
||||||
|
try:
|
||||||
|
output = self.tng_dataloader()
|
||||||
|
warnings.warn("tng_dataloader has been renamed to train_dataloader since v0.5.0",
|
||||||
|
DeprecationWarning)
|
||||||
|
return output
|
||||||
|
except NotImplementedError:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
@data_loader
|
@data_loader
|
||||||
def test_dataloader(self):
|
def test_dataloader(self):
|
||||||
@@ -159,14 +287,48 @@ class LightningModule(GradInformation, ModelIO, ModelHooks):
|
|||||||
|
|
||||||
return model
|
return model
|
||||||
|
|
||||||
def summarize(self):
|
@classmethod
|
||||||
model_summary = ModelSummary(self)
|
def load_from_checkpoint(cls, checkpoint_path):
|
||||||
print(model_summary)
|
"""
|
||||||
|
Primary way of loading model from a checkpoint
|
||||||
|
:param checkpoint_path:
|
||||||
|
:param map_location: dic for mapping storage {'cuda:1':'cuda:0'}
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
# load on CPU only to avoid OOM issues
|
||||||
|
# then its up to user to put back on GPUs
|
||||||
|
checkpoint = torch.load(checkpoint_path, map_location=lambda storage, loc: storage)
|
||||||
|
try:
|
||||||
|
ckpt_hparams = checkpoint['hparams']
|
||||||
|
except KeyError:
|
||||||
|
raise IOError(
|
||||||
|
"Checkpoint does not contain hyperparameters. Are your model hyperparameters stored"
|
||||||
|
"in self.hparams?"
|
||||||
|
)
|
||||||
|
hparams = Namespace(**ckpt_hparams)
|
||||||
|
|
||||||
|
# load the state_dict on the model automatically
|
||||||
|
model = cls(hparams)
|
||||||
|
model.load_state_dict(checkpoint['state_dict'])
|
||||||
|
|
||||||
|
# give model a chance to load something
|
||||||
|
model.on_load_checkpoint(checkpoint)
|
||||||
|
|
||||||
|
return model
|
||||||
|
|
||||||
|
def summarize(self, mode):
|
||||||
|
model_summary = ModelSummary(self, mode=mode)
|
||||||
|
logging.info(model_summary)
|
||||||
|
|
||||||
def freeze(self):
|
def freeze(self):
|
||||||
for param in self.parameters():
|
for param in self.parameters():
|
||||||
param.requires_grad = False
|
param.requires_grad = False
|
||||||
|
|
||||||
|
self.eval()
|
||||||
|
|
||||||
def unfreeze(self):
|
def unfreeze(self):
|
||||||
for param in self.parameters():
|
for param in self.parameters():
|
||||||
param.requires_grad = True
|
param.requires_grad = True
|
||||||
|
|
||||||
|
self.train()
|
||||||
|
|||||||
@@ -1,18 +1,4 @@
|
|||||||
import os
|
|
||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
import torch.nn as nn
|
|
||||||
import torch.nn.functional as F
|
|
||||||
from torch import optim
|
|
||||||
from torch.utils.data import DataLoader
|
|
||||||
from torch.utils.data.distributed import DistributedSampler
|
|
||||||
from torchvision.datasets import MNIST
|
|
||||||
from torchvision import transforms
|
|
||||||
from test_tube import HyperOptArgumentParser
|
|
||||||
|
|
||||||
from pytorch_lightning.root_module.root_module import LightningModule
|
|
||||||
from pytorch_lightning import data_loader
|
|
||||||
|
|
||||||
from .lm_test_module_base import LightningTestModelBase
|
from .lm_test_module_base import LightningTestModelBase
|
||||||
from .lm_test_module_mixins import LightningValidationMixin, LightningTestMixin
|
from .lm_test_module_mixins import LightningValidationMixin, LightningTestMixin
|
||||||
|
|||||||
@@ -7,12 +7,16 @@ import torch.nn.functional as F
|
|||||||
from torch import optim
|
from torch import optim
|
||||||
from torch.utils.data import DataLoader
|
from torch.utils.data import DataLoader
|
||||||
from torch.utils.data.distributed import DistributedSampler
|
from torch.utils.data.distributed import DistributedSampler
|
||||||
from torchvision.datasets import MNIST
|
|
||||||
from torchvision import transforms
|
from torchvision import transforms
|
||||||
from test_tube import HyperOptArgumentParser
|
from torchvision.datasets import MNIST
|
||||||
|
try:
|
||||||
|
from test_tube import HyperOptArgumentParser
|
||||||
|
except ImportError:
|
||||||
|
# TODO: this should be discussed and moved out of this package
|
||||||
|
raise ImportError('Missing test-tube package.')
|
||||||
|
|
||||||
from pytorch_lightning.root_module.root_module import LightningModule
|
|
||||||
from pytorch_lightning import data_loader
|
from pytorch_lightning import data_loader
|
||||||
|
from pytorch_lightning.root_module.root_module import LightningModule
|
||||||
|
|
||||||
|
|
||||||
class LightningTestModelBase(LightningModule):
|
class LightningTestModelBase(LightningModule):
|
||||||
@@ -104,7 +108,8 @@ class LightningTestModelBase(LightningModule):
|
|||||||
if self.trainer.batch_nb % 1 == 0:
|
if self.trainer.batch_nb % 1 == 0:
|
||||||
output = OrderedDict({
|
output = OrderedDict({
|
||||||
'loss': loss_val,
|
'loss': loss_val,
|
||||||
'progress_bar': {'some_val': loss_val * loss_val}
|
'progress_bar': {'some_val': loss_val * loss_val},
|
||||||
|
'log': {'train_some_val': loss_val * loss_val},
|
||||||
})
|
})
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|||||||
@@ -1,17 +1,7 @@
|
|||||||
import os
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
import torch.nn as nn
|
|
||||||
import torch.nn.functional as F
|
|
||||||
from torch import optim
|
|
||||||
from torch.utils.data import DataLoader
|
|
||||||
from torch.utils.data.distributed import DistributedSampler
|
|
||||||
from torchvision.datasets import MNIST
|
|
||||||
from torchvision import transforms
|
|
||||||
from test_tube import HyperOptArgumentParser
|
|
||||||
|
|
||||||
from pytorch_lightning.root_module.root_module import LightningModule
|
|
||||||
from pytorch_lightning import data_loader
|
from pytorch_lightning import data_loader
|
||||||
|
|
||||||
|
|
||||||
@@ -90,13 +80,13 @@ class LightningValidationMixin(LightningValidationStepMixin):
|
|||||||
val_loss = output['val_loss']
|
val_loss = output['val_loss']
|
||||||
|
|
||||||
# reduce manually when using dp
|
# reduce manually when using dp
|
||||||
if self.trainer.use_dp:
|
if self.trainer.use_dp or self.trainer.use_ddp2:
|
||||||
val_loss = torch.mean(val_loss)
|
val_loss = torch.mean(val_loss)
|
||||||
val_loss_mean += val_loss
|
val_loss_mean += val_loss
|
||||||
|
|
||||||
# reduce manually when using dp
|
# reduce manually when using dp
|
||||||
val_acc = output['val_acc']
|
val_acc = output['val_acc']
|
||||||
if self.trainer.use_dp:
|
if self.trainer.use_dp or self.trainer.use_ddp2:
|
||||||
val_acc = torch.mean(val_acc)
|
val_acc = torch.mean(val_acc)
|
||||||
|
|
||||||
val_acc_mean += val_acc
|
val_acc_mean += val_acc
|
||||||
@@ -105,7 +95,7 @@ class LightningValidationMixin(LightningValidationStepMixin):
|
|||||||
val_acc_mean /= len(outputs)
|
val_acc_mean /= len(outputs)
|
||||||
|
|
||||||
tqdm_dict = {'val_loss': val_loss_mean.item(), 'val_acc': val_acc_mean.item()}
|
tqdm_dict = {'val_loss': val_loss_mean.item(), 'val_acc': val_acc_mean.item()}
|
||||||
results = {'progress_bar': tqdm_dict}
|
results = {'progress_bar': tqdm_dict, 'log': tqdm_dict}
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
try:
|
||||||
|
from apex import amp
|
||||||
|
|
||||||
|
APEX_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
APEX_AVAILABLE = False
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
class TrainerAMPMixin(object):
|
||||||
|
|
||||||
|
def init_amp(self, use_amp):
|
||||||
|
self.use_amp = use_amp and APEX_AVAILABLE
|
||||||
|
if self.use_amp:
|
||||||
|
logging.info('using 16bit precision')
|
||||||
|
|
||||||
|
if use_amp and not APEX_AVAILABLE: # pragma: no cover
|
||||||
|
msg = """
|
||||||
|
You set use_amp=True but do not have apex installed.
|
||||||
|
Install apex first using this guide and rerun with use_amp=True:
|
||||||
|
https://github.com/NVIDIA/apex#linux
|
||||||
|
|
||||||
|
this run will NOT use 16 bit precision
|
||||||
|
"""
|
||||||
|
raise ModuleNotFoundError(msg)
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from pytorch_lightning.callbacks import ModelCheckpoint, EarlyStopping
|
||||||
|
from pytorch_lightning.logging import TestTubeLogger
|
||||||
|
|
||||||
|
|
||||||
|
class TrainerCallbackConfigMixin(object):
|
||||||
|
def configure_checkpoint_callback(self):
|
||||||
|
"""
|
||||||
|
Weight path set in this priority:
|
||||||
|
Checkpoint_callback's path (if passed in).
|
||||||
|
User provided weights_saved_path
|
||||||
|
Otherwise use os.getcwd()
|
||||||
|
"""
|
||||||
|
if self.checkpoint_callback is True:
|
||||||
|
# init a default one
|
||||||
|
if self.logger is not None:
|
||||||
|
ckpt_path = os.path.join(
|
||||||
|
self.default_save_path,
|
||||||
|
self.logger.name,
|
||||||
|
f'version_{self.logger.version}',
|
||||||
|
"checkpoints"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
ckpt_path = os.path.join(self.default_save_path, "checkpoints")
|
||||||
|
|
||||||
|
self.checkpoint_callback = ModelCheckpoint(
|
||||||
|
filepath=ckpt_path
|
||||||
|
)
|
||||||
|
elif self.checkpoint_callback is False:
|
||||||
|
self.checkpoint_callback = None
|
||||||
|
|
||||||
|
if self.checkpoint_callback:
|
||||||
|
# set the path for the callbacks
|
||||||
|
self.checkpoint_callback.save_function = self.save_checkpoint
|
||||||
|
|
||||||
|
# if checkpoint callback used, then override the weights path
|
||||||
|
self.weights_save_path = self.checkpoint_callback.filepath
|
||||||
|
|
||||||
|
# if weights_save_path is still none here, set to current working dir
|
||||||
|
if self.weights_save_path is None:
|
||||||
|
self.weights_save_path = self.default_save_path
|
||||||
|
|
||||||
|
def configure_early_stopping(self, early_stop_callback, logger):
|
||||||
|
if early_stop_callback is True:
|
||||||
|
self.early_stop_callback = EarlyStopping(
|
||||||
|
monitor='val_loss',
|
||||||
|
patience=3,
|
||||||
|
verbose=True,
|
||||||
|
mode='min'
|
||||||
|
)
|
||||||
|
self.enable_early_stop = True
|
||||||
|
elif not early_stop_callback:
|
||||||
|
self.early_stop_callback = None
|
||||||
|
self.enable_early_stop = False
|
||||||
|
else:
|
||||||
|
self.early_stop_callback = early_stop_callback
|
||||||
|
self.enable_early_stop = True
|
||||||
|
|
||||||
|
# configure logger
|
||||||
|
if logger is True:
|
||||||
|
# default logger
|
||||||
|
self.logger = TestTubeLogger(
|
||||||
|
save_dir=self.default_save_path,
|
||||||
|
version=self.slurm_job_id,
|
||||||
|
name='lightning_logs'
|
||||||
|
)
|
||||||
|
self.logger.rank = 0
|
||||||
|
elif logger is False:
|
||||||
|
self.logger = None
|
||||||
|
else:
|
||||||
|
self.logger = logger
|
||||||
|
self.logger.rank = 0
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
import warnings
|
||||||
|
|
||||||
|
import torch.distributed as dist
|
||||||
|
from torch.utils.data import IterableDataset
|
||||||
|
from torch.utils.data.distributed import DistributedSampler
|
||||||
|
|
||||||
|
from pytorch_lightning.utilities.debugging import MisconfigurationException
|
||||||
|
|
||||||
|
try:
|
||||||
|
from apex import amp
|
||||||
|
|
||||||
|
APEX_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
APEX_AVAILABLE = False
|
||||||
|
|
||||||
|
|
||||||
|
class TrainerDataLoadingMixin(object):
|
||||||
|
def init_train_dataloader(self, model):
|
||||||
|
"""
|
||||||
|
Dataloaders are provided by the model
|
||||||
|
:param model:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
self.get_train_dataloader = model.train_dataloader
|
||||||
|
|
||||||
|
# determine number of training batches
|
||||||
|
if isinstance(self.get_train_dataloader(), IterableDataset):
|
||||||
|
self.nb_training_batches = float('inf')
|
||||||
|
else:
|
||||||
|
self.nb_training_batches = len(self.get_train_dataloader())
|
||||||
|
self.nb_training_batches = int(self.nb_training_batches * self.train_percent_check)
|
||||||
|
|
||||||
|
# determine when to check validation
|
||||||
|
# if int passed in, val checks that often
|
||||||
|
# otherwise, it checks in [0, 1.0] % range of a training epoch
|
||||||
|
if isinstance(self.val_check_interval, int):
|
||||||
|
self.val_check_batch = self.val_check_interval
|
||||||
|
else:
|
||||||
|
self.val_check_batch = int(self.nb_training_batches * self.val_check_interval)
|
||||||
|
self.val_check_batch = max(1, self.val_check_batch)
|
||||||
|
|
||||||
|
on_ddp = self.use_ddp or self.use_ddp2
|
||||||
|
if on_ddp and not isinstance(self.get_train_dataloader().sampler, DistributedSampler):
|
||||||
|
msg = """
|
||||||
|
You're using multiple gpus and multiple nodes without using a DistributedSampler
|
||||||
|
to assign a subset of your data to each process. To silence this warning, pass a
|
||||||
|
DistributedSampler to your DataLoader.
|
||||||
|
|
||||||
|
ie: this:
|
||||||
|
dataset = myDataset()
|
||||||
|
dataloader = Dataloader(dataset)
|
||||||
|
|
||||||
|
becomes:
|
||||||
|
dataset = myDataset()
|
||||||
|
dist_sampler = torch.utils.data.distributed.DistributedSampler(dataset)
|
||||||
|
dataloader = Dataloader(dataset, sampler=dist_sampler)
|
||||||
|
|
||||||
|
If you want each process to load the full dataset, ignore this warning.
|
||||||
|
"""
|
||||||
|
if msg not in self.shown_warnings and self.proc_rank == 0:
|
||||||
|
self.shown_warnings.add(msg)
|
||||||
|
warnings.warn(msg)
|
||||||
|
|
||||||
|
def init_val_dataloader(self, model):
|
||||||
|
"""
|
||||||
|
Dataloaders are provided by the model
|
||||||
|
:param model:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
self.get_val_dataloaders = model.val_dataloader
|
||||||
|
|
||||||
|
# determine number of validation batches
|
||||||
|
# val datasets could be none, 1 or 2+
|
||||||
|
if self.get_val_dataloaders() is not None:
|
||||||
|
self.nb_val_batches = sum(len(dataloader) for dataloader in self.get_val_dataloaders())
|
||||||
|
self.nb_val_batches = int(self.nb_val_batches * self.val_percent_check)
|
||||||
|
self.nb_val_batches = max(1, self.nb_val_batches)
|
||||||
|
|
||||||
|
on_ddp = self.use_ddp or self.use_ddp2
|
||||||
|
if on_ddp and self.get_val_dataloaders() is not None:
|
||||||
|
for dataloader in self.get_val_dataloaders():
|
||||||
|
if not isinstance(dataloader.sampler, DistributedSampler):
|
||||||
|
msg = """
|
||||||
|
Your val_dataloader(s) don't use DistributedSampler.
|
||||||
|
|
||||||
|
You're using multiple gpus and multiple nodes without using a
|
||||||
|
DistributedSampler to assign a subset of your data to each process.
|
||||||
|
To silence this warning, pass a DistributedSampler to your DataLoader.
|
||||||
|
|
||||||
|
ie: this:
|
||||||
|
dataset = myDataset()
|
||||||
|
dataloader = Dataloader(dataset)
|
||||||
|
|
||||||
|
becomes:
|
||||||
|
dataset = myDataset()
|
||||||
|
dist_sampler = torch.utils.data.distributed.DistributedSampler(dataset)
|
||||||
|
dataloader = Dataloader(dataset, sampler=dist_sampler)
|
||||||
|
|
||||||
|
If you want each process to load the full dataset, ignore this warning.
|
||||||
|
"""
|
||||||
|
if msg not in self.shown_warnings and self.proc_rank == 0:
|
||||||
|
self.shown_warnings.add(msg)
|
||||||
|
warnings.warn(msg)
|
||||||
|
break
|
||||||
|
|
||||||
|
def init_test_dataloader(self, model):
|
||||||
|
"""
|
||||||
|
Dataloaders are provided by the model
|
||||||
|
:param model:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.get_test_dataloaders = model.test_dataloader
|
||||||
|
|
||||||
|
# determine number of test batches
|
||||||
|
if self.get_test_dataloaders() is not None:
|
||||||
|
len_sum = sum(len(dataloader) for dataloader in self.get_test_dataloaders())
|
||||||
|
self.nb_test_batches = len_sum
|
||||||
|
self.nb_test_batches = int(self.nb_test_batches * self.test_percent_check)
|
||||||
|
self.nb_test_batches = max(1, self.nb_test_batches)
|
||||||
|
|
||||||
|
on_ddp = self.use_ddp or self.use_ddp2
|
||||||
|
if on_ddp and self.get_test_dataloaders() is not None:
|
||||||
|
for dataloader in self.get_test_dataloaders():
|
||||||
|
if not isinstance(dataloader.sampler, DistributedSampler):
|
||||||
|
msg = """
|
||||||
|
Your test_dataloader(s) don't use DistributedSampler.
|
||||||
|
|
||||||
|
You're using multiple gpus and multiple nodes without using a
|
||||||
|
DistributedSampler to assign a subset of your data to each process.
|
||||||
|
To silence this warning, pass a DistributedSampler to your DataLoader.
|
||||||
|
|
||||||
|
ie: this:
|
||||||
|
dataset = myDataset()
|
||||||
|
dataloader = Dataloader(dataset)
|
||||||
|
|
||||||
|
becomes:
|
||||||
|
dataset = myDataset()
|
||||||
|
dist_sampler = torch.utils.data.distributed.DistributedSampler(dataset)
|
||||||
|
dataloader = Dataloader(dataset, sampler=dist_sampler)
|
||||||
|
|
||||||
|
If you want each process to load the full dataset, ignore this warning.
|
||||||
|
"""
|
||||||
|
if msg not in self.shown_warnings and self.proc_rank == 0:
|
||||||
|
self.shown_warnings.add(msg)
|
||||||
|
warnings.warn(msg)
|
||||||
|
break
|
||||||
|
|
||||||
|
def get_dataloaders(self, model):
|
||||||
|
"""
|
||||||
|
Dataloaders are provided by the model
|
||||||
|
:param model:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.init_train_dataloader(model)
|
||||||
|
self.init_test_dataloader(model)
|
||||||
|
self.init_val_dataloader(model)
|
||||||
|
|
||||||
|
if self.use_ddp or self.use_ddp2:
|
||||||
|
# wait for all processes to catch up
|
||||||
|
dist.barrier()
|
||||||
|
|
||||||
|
# load each dataloader
|
||||||
|
self.get_train_dataloader()
|
||||||
|
self.get_test_dataloaders()
|
||||||
|
self.get_val_dataloaders()
|
||||||
|
|
||||||
|
# support IterableDataset for train data
|
||||||
|
self.is_iterable_train_dataloader = isinstance(self.get_train_dataloader(), IterableDataset)
|
||||||
|
if self.is_iterable_train_dataloader and not isinstance(self.val_check_interval, int):
|
||||||
|
m = '''
|
||||||
|
When using an iterableDataset for train_dataloader,
|
||||||
|
Trainer(val_check_interval) must be an int.
|
||||||
|
An int k specifies checking validation every k training batches
|
||||||
|
'''
|
||||||
|
raise MisconfigurationException(m)
|
||||||
|
|
||||||
|
def determine_data_use_amount(self, train_percent_check, val_percent_check,
|
||||||
|
test_percent_check, overfit_pct):
|
||||||
|
"""
|
||||||
|
Use less data for debugging purposes
|
||||||
|
"""
|
||||||
|
self.train_percent_check = train_percent_check
|
||||||
|
self.val_percent_check = val_percent_check
|
||||||
|
self.test_percent_check = test_percent_check
|
||||||
|
if overfit_pct > 0:
|
||||||
|
self.train_percent_check = overfit_pct
|
||||||
|
self.val_percent_check = overfit_pct
|
||||||
|
self.test_percent_check = overfit_pct
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
import warnings
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from pytorch_lightning.utilities.debugging import MisconfigurationException
|
||||||
|
|
||||||
|
try:
|
||||||
|
from apex import amp
|
||||||
|
|
||||||
|
APEX_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
APEX_AVAILABLE = False
|
||||||
|
|
||||||
|
|
||||||
|
class TrainerDDPMixin(object):
|
||||||
|
def set_distributed_mode(self, distributed_backend, nb_gpu_nodes):
|
||||||
|
# skip for CPU
|
||||||
|
if self.num_gpus == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
# single GPU case
|
||||||
|
# in single gpu case we allow ddp so we can train on multiple
|
||||||
|
# nodes, 1 gpu per node
|
||||||
|
if self.num_gpus == 1:
|
||||||
|
self.single_gpu = True
|
||||||
|
|
||||||
|
if distributed_backend is not None:
|
||||||
|
self.use_dp = distributed_backend == 'dp'
|
||||||
|
self.use_ddp = distributed_backend == 'ddp'
|
||||||
|
self.use_ddp2 = distributed_backend == 'ddp2'
|
||||||
|
|
||||||
|
# disable single gpu when using ddp2
|
||||||
|
if self.use_ddp2:
|
||||||
|
self.single_gpu = False
|
||||||
|
|
||||||
|
# multiple GPU case
|
||||||
|
elif self.num_gpus > 1:
|
||||||
|
if distributed_backend is not None:
|
||||||
|
# DP, DDP case
|
||||||
|
self.use_dp = distributed_backend == 'dp'
|
||||||
|
self.use_ddp = distributed_backend == 'ddp'
|
||||||
|
self.use_ddp2 = distributed_backend == 'ddp2'
|
||||||
|
|
||||||
|
elif distributed_backend is None:
|
||||||
|
m = 'When using multiple GPUs set ' \
|
||||||
|
'Trainer(distributed_backend=dp) (or ddp)'
|
||||||
|
raise MisconfigurationException(m)
|
||||||
|
|
||||||
|
# throw error to force user ddp or ddp2 choice
|
||||||
|
if nb_gpu_nodes > 1 and not (self.use_ddp2 or self.use_ddp): # pragma: no cover
|
||||||
|
w = 'DataParallel does not support nb_gpu_nodes > 1. ' \
|
||||||
|
'Switching to DistributedDataParallel for you. ' \
|
||||||
|
'To silence this warning set distributed_backend=ddp' \
|
||||||
|
'or distributed_backend=ddp2'
|
||||||
|
raise MisconfigurationException(w)
|
||||||
|
|
||||||
|
logging.info(f'gpu available: {torch.cuda.is_available()}, used: {self.on_gpu}')
|
||||||
|
|
||||||
|
def configure_slurm_ddp(self, nb_gpu_nodes):
|
||||||
|
self.is_slurm_managing_tasks = False
|
||||||
|
|
||||||
|
# extract SLURM flag vars
|
||||||
|
# whenever we have the correct number of tasks, we let slurm manage processes
|
||||||
|
# otherwise we launch the required number of processes
|
||||||
|
if self.use_ddp:
|
||||||
|
self.nb_requested_gpus = self.num_gpus * nb_gpu_nodes
|
||||||
|
self.nb_slurm_tasks = 0
|
||||||
|
try:
|
||||||
|
self.nb_slurm_tasks = int(os.environ['SLURM_NTASKS'])
|
||||||
|
self.is_slurm_managing_tasks = self.nb_slurm_tasks == self.nb_requested_gpus
|
||||||
|
|
||||||
|
# in interactive mode we don't manage tasks
|
||||||
|
job_name = os.environ['SLURM_JOB_NAME']
|
||||||
|
if job_name == 'bash':
|
||||||
|
self.is_slurm_managing_tasks = False
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
# likely not on slurm, so set the slurm managed flag to false
|
||||||
|
self.is_slurm_managing_tasks = False
|
||||||
|
|
||||||
|
# used for tests only, set this flag to simulate slurm managing a task
|
||||||
|
try:
|
||||||
|
should_fake = int(os.environ['FAKE_SLURM_MANAGING_TASKS'])
|
||||||
|
if should_fake:
|
||||||
|
self.is_slurm_managing_tasks = True
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_nvidia_flags(self, is_slurm_managing_tasks, data_parallel_device_ids):
|
||||||
|
if data_parallel_device_ids is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# set the correct cuda visible devices (using pci order)
|
||||||
|
os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
|
||||||
|
|
||||||
|
# when slurm is managing the task it sets the visible devices
|
||||||
|
if not is_slurm_managing_tasks:
|
||||||
|
if type(data_parallel_device_ids) is int:
|
||||||
|
id_str = ','.join(str(x) for x in list(range(data_parallel_device_ids)))
|
||||||
|
os.environ["CUDA_VISIBLE_DEVICES"] = id_str
|
||||||
|
else:
|
||||||
|
gpu_str = ','.join([str(x) for x in data_parallel_device_ids])
|
||||||
|
os.environ["CUDA_VISIBLE_DEVICES"] = gpu_str
|
||||||
|
|
||||||
|
logging.info(f'VISIBLE GPUS: {os.environ["CUDA_VISIBLE_DEVICES"]}')
|
||||||
|
|
||||||
|
def ddp_train(self, gpu_nb, model):
|
||||||
|
"""
|
||||||
|
Entry point into a DP thread
|
||||||
|
:param gpu_nb:
|
||||||
|
:param model:
|
||||||
|
:param cluster_obj:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
# node rank using relative slurm id
|
||||||
|
# otherwise default to node rank 0
|
||||||
|
try:
|
||||||
|
node_id = os.environ['SLURM_NODEID']
|
||||||
|
self.node_rank = int(node_id)
|
||||||
|
except Exception:
|
||||||
|
self.node_rank = 0
|
||||||
|
|
||||||
|
# show progressbar only on progress_rank 0
|
||||||
|
self.show_progress_bar = self.show_progress_bar and self.node_rank == 0 and gpu_nb == 0
|
||||||
|
|
||||||
|
# determine which process we are and world size
|
||||||
|
if self.use_ddp:
|
||||||
|
self.proc_rank = self.node_rank * self.num_gpus + gpu_nb
|
||||||
|
self.world_size = self.nb_gpu_nodes * self.num_gpus
|
||||||
|
|
||||||
|
elif self.use_ddp2:
|
||||||
|
self.proc_rank = self.node_rank
|
||||||
|
self.world_size = self.nb_gpu_nodes
|
||||||
|
|
||||||
|
# let the exp know the rank to avoid overwriting logs
|
||||||
|
if self.logger is not None:
|
||||||
|
self.logger.rank = self.proc_rank
|
||||||
|
|
||||||
|
# set up server using proc 0's ip address
|
||||||
|
# try to init for 20 times at max in case ports are taken
|
||||||
|
# where to store ip_table
|
||||||
|
model.trainer = self
|
||||||
|
model.init_ddp_connection(self.proc_rank, self.world_size)
|
||||||
|
|
||||||
|
# CHOOSE OPTIMIZER
|
||||||
|
# allow for lr schedulers as well
|
||||||
|
self.optimizers, self.lr_schedulers = self.init_optimizers(model.configure_optimizers())
|
||||||
|
|
||||||
|
# MODEL
|
||||||
|
# copy model to each gpu
|
||||||
|
if self.distributed_backend == 'ddp':
|
||||||
|
torch.cuda.set_device(gpu_nb)
|
||||||
|
model.cuda(gpu_nb)
|
||||||
|
|
||||||
|
# set model properties before going into wrapper
|
||||||
|
self.copy_trainer_model_properties(model)
|
||||||
|
|
||||||
|
# override root GPU
|
||||||
|
self.root_gpu = gpu_nb
|
||||||
|
|
||||||
|
# AMP
|
||||||
|
# run through amp wrapper before going to distributed DP
|
||||||
|
if self.use_amp:
|
||||||
|
# An example
|
||||||
|
model, optimizers = model.configure_apex(amp, model, self.optimizers, self.amp_level)
|
||||||
|
self.optimizers = optimizers
|
||||||
|
|
||||||
|
# DDP2 uses all GPUs on the machine
|
||||||
|
if self.distributed_backend == 'ddp':
|
||||||
|
device_ids = [gpu_nb]
|
||||||
|
elif self.use_ddp2:
|
||||||
|
device_ids = self.data_parallel_device_ids
|
||||||
|
|
||||||
|
# allow user to configure ddp
|
||||||
|
model = model.configure_ddp(model, device_ids)
|
||||||
|
|
||||||
|
# continue training routine
|
||||||
|
self.run_pretrain_routine(model)
|
||||||
|
|
||||||
|
def resolve_root_node_address(self, root_node):
|
||||||
|
if '[' in root_node:
|
||||||
|
name = root_node.split('[')[0]
|
||||||
|
number = root_node.split(',')[0]
|
||||||
|
if '-' in number:
|
||||||
|
number = number.split('-')[0]
|
||||||
|
|
||||||
|
number = re.sub('[^0-9]', '', number)
|
||||||
|
root_node = name + number
|
||||||
|
|
||||||
|
return root_node
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
import torch
|
||||||
|
|
||||||
|
from pytorch_lightning.pt_overrides.override_data_parallel import (
|
||||||
|
LightningDistributedDataParallel, LightningDataParallel)
|
||||||
|
from pytorch_lightning.utilities.debugging import MisconfigurationException
|
||||||
|
|
||||||
|
try:
|
||||||
|
from apex import amp
|
||||||
|
|
||||||
|
APEX_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
APEX_AVAILABLE = False
|
||||||
|
|
||||||
|
|
||||||
|
class TrainerDPMixin(object):
|
||||||
|
def copy_trainer_model_properties(self, model):
|
||||||
|
if isinstance(model, LightningDataParallel):
|
||||||
|
ref_model = model.module
|
||||||
|
elif isinstance(model, LightningDistributedDataParallel):
|
||||||
|
ref_model = model.module
|
||||||
|
else:
|
||||||
|
ref_model = model
|
||||||
|
|
||||||
|
for m in [model, ref_model]:
|
||||||
|
m.trainer = self
|
||||||
|
m.on_gpu = self.on_gpu
|
||||||
|
m.use_dp = self.use_dp
|
||||||
|
m.use_ddp2 = self.use_ddp2
|
||||||
|
m.use_ddp = self.use_ddp
|
||||||
|
m.use_amp = self.use_amp
|
||||||
|
m.testing = self.testing
|
||||||
|
m.single_gpu = self.single_gpu
|
||||||
|
|
||||||
|
def transfer_batch_to_gpu(self, batch, gpu_id):
|
||||||
|
# base case: object can be directly moved using `cuda` or `to`
|
||||||
|
if callable(getattr(batch, 'cuda', None)):
|
||||||
|
return batch.cuda(gpu_id)
|
||||||
|
|
||||||
|
elif callable(getattr(batch, 'to', None)):
|
||||||
|
return batch.to(torch.device('cuda', gpu_id))
|
||||||
|
|
||||||
|
# when list
|
||||||
|
elif isinstance(batch, list):
|
||||||
|
for i, x in enumerate(batch):
|
||||||
|
batch[i] = self.transfer_batch_to_gpu(x, gpu_id)
|
||||||
|
return batch
|
||||||
|
|
||||||
|
# when tuple
|
||||||
|
elif isinstance(batch, tuple):
|
||||||
|
batch = list(batch)
|
||||||
|
for i, x in enumerate(batch):
|
||||||
|
batch[i] = self.transfer_batch_to_gpu(x, gpu_id)
|
||||||
|
return tuple(batch)
|
||||||
|
|
||||||
|
# when dict
|
||||||
|
elif isinstance(batch, dict):
|
||||||
|
for k, v in batch.items():
|
||||||
|
batch[k] = self.transfer_batch_to_gpu(v, gpu_id)
|
||||||
|
|
||||||
|
return batch
|
||||||
|
|
||||||
|
# nothing matches, return the value as is without transform
|
||||||
|
return batch
|
||||||
|
|
||||||
|
def single_gpu_train(self, model):
|
||||||
|
# CHOOSE OPTIMIZER
|
||||||
|
# allow for lr schedulers as well
|
||||||
|
self.optimizers, self.lr_schedulers = self.init_optimizers(model.configure_optimizers())
|
||||||
|
|
||||||
|
model.cuda(self.root_gpu)
|
||||||
|
|
||||||
|
if self.use_amp:
|
||||||
|
# An example
|
||||||
|
model, optimizers = model.configure_apex(amp, model, self.optimizers, self.amp_level)
|
||||||
|
self.optimizers = optimizers
|
||||||
|
|
||||||
|
self.run_pretrain_routine(model)
|
||||||
|
|
||||||
|
def dp_train(self, model):
|
||||||
|
|
||||||
|
# CHOOSE OPTIMIZER
|
||||||
|
# allow for lr schedulers as well
|
||||||
|
self.optimizers, self.lr_schedulers = self.init_optimizers(model.configure_optimizers())
|
||||||
|
|
||||||
|
model.cuda(self.root_gpu)
|
||||||
|
|
||||||
|
# check for this bug (amp + dp + !01 doesn't work)
|
||||||
|
# https://github.com/NVIDIA/apex/issues/227
|
||||||
|
if self.use_dp and self.use_amp:
|
||||||
|
m = f"""
|
||||||
|
Amp level {self.amp_level} with DataParallel is not supported.
|
||||||
|
See this note from NVIDIA for more info: https://github.com/NVIDIA/apex/issues/227.
|
||||||
|
We recommend you switch to ddp if you want to use amp
|
||||||
|
"""
|
||||||
|
raise MisconfigurationException(m)
|
||||||
|
|
||||||
|
# create list of device ids
|
||||||
|
device_ids = self.data_parallel_device_ids
|
||||||
|
if type(device_ids) is int:
|
||||||
|
device_ids = list(range(device_ids))
|
||||||
|
|
||||||
|
model = LightningDataParallel(model, device_ids=device_ids)
|
||||||
|
|
||||||
|
self.run_pretrain_routine(model)
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_parse_gpu_string_input(s):
|
||||||
|
if type(s) is str:
|
||||||
|
if s == '-1':
|
||||||
|
return -1
|
||||||
|
else:
|
||||||
|
return [int(x.strip()) for x in s.split(',')]
|
||||||
|
else:
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_available_gpus():
|
||||||
|
"""
|
||||||
|
:return: a list of all available gpus
|
||||||
|
"""
|
||||||
|
return list(range(torch.cuda.device_count()))
|
||||||
|
|
||||||
|
|
||||||
|
def check_gpus_data_type(gpus):
|
||||||
|
"""
|
||||||
|
:param gpus: gpus parameter as passed to the Trainer
|
||||||
|
Function checks that it is one of: None, Int, String or List
|
||||||
|
Throws otherwise
|
||||||
|
:return: return unmodified gpus variable
|
||||||
|
"""
|
||||||
|
|
||||||
|
if (gpus is not None and
|
||||||
|
type(gpus) is not int and
|
||||||
|
type(gpus) is not str and
|
||||||
|
type(gpus) is not list): # noqa E129
|
||||||
|
raise MisconfigurationException("GPUs must be int, string or list of ints or None.")
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_parse_gpu_input_to_list(gpus):
|
||||||
|
assert gpus is not None
|
||||||
|
if isinstance(gpus, list):
|
||||||
|
return gpus
|
||||||
|
else: # must be an int
|
||||||
|
if not gpus: # gpus==0
|
||||||
|
return None
|
||||||
|
elif gpus == -1:
|
||||||
|
return get_all_available_gpus()
|
||||||
|
else:
|
||||||
|
return list(range(gpus))
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_gpu_ids(gpus):
|
||||||
|
"""
|
||||||
|
:param gpus: list of ints corresponding to GPU indices
|
||||||
|
Checks that each of the GPUs in the list is actually available.
|
||||||
|
Throws if any of the GPUs is not available.
|
||||||
|
:return: unmodified gpus variable
|
||||||
|
"""
|
||||||
|
all_available_gpus = get_all_available_gpus()
|
||||||
|
for gpu in gpus:
|
||||||
|
if gpu not in all_available_gpus:
|
||||||
|
message = f"""
|
||||||
|
Non-available gpu index {gpu} specified:
|
||||||
|
Available gpu indices are: {all_available_gpus}
|
||||||
|
"""
|
||||||
|
raise MisconfigurationException(message)
|
||||||
|
return gpus
|
||||||
|
|
||||||
|
|
||||||
|
def parse_gpu_ids(gpus):
|
||||||
|
"""
|
||||||
|
:param gpus: Int, string or list
|
||||||
|
An int -1 or string '-1' indicate that all available GPUs should be used.
|
||||||
|
A list of ints or a string containing list of comma separated integers
|
||||||
|
indicates specific GPUs to use
|
||||||
|
An int 0 means that no GPUs should be used
|
||||||
|
Any int N > 0 indicates that GPUs [0..N) should be used.
|
||||||
|
:return: List of gpus to be used
|
||||||
|
|
||||||
|
If no GPUs are available but the value of gpus variable indicates request for GPUs
|
||||||
|
then a misconfiguration exception is raised.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Check that gpus param is None, Int, String or List
|
||||||
|
check_gpus_data_type(gpus)
|
||||||
|
|
||||||
|
# Handle the case when no gpus are requested
|
||||||
|
if gpus is None or type(gpus) is int and gpus == 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# We know user requested GPUs therefore if some of the
|
||||||
|
# requested GPUs are not available an exception is thrown.
|
||||||
|
|
||||||
|
gpus = normalize_parse_gpu_string_input(gpus)
|
||||||
|
gpus = normalize_parse_gpu_input_to_list(gpus)
|
||||||
|
gpus = sanitize_gpu_ids(gpus)
|
||||||
|
|
||||||
|
if not gpus:
|
||||||
|
raise MisconfigurationException("GPUs requested but non are available.")
|
||||||
|
return gpus
|
||||||
|
|
||||||
|
|
||||||
|
def determine_root_gpu_device(gpus):
|
||||||
|
"""
|
||||||
|
:param gpus: non empty list of ints representing which gpus to use
|
||||||
|
:return: designated root GPU device
|
||||||
|
"""
|
||||||
|
if gpus is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
assert isinstance(gpus, list), "gpus should be a list"
|
||||||
|
assert len(gpus), "gpus should be a non empty list"
|
||||||
|
|
||||||
|
# set root gpu
|
||||||
|
root_gpu = gpus[0]
|
||||||
|
|
||||||
|
return root_gpu
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
import torch
|
||||||
|
import tqdm
|
||||||
|
|
||||||
|
from pytorch_lightning.utilities.debugging import MisconfigurationException
|
||||||
|
|
||||||
|
|
||||||
|
class TrainerEvaluationLoopMixin(object):
|
||||||
|
|
||||||
|
def evaluate(self, model, dataloaders, max_batches, test=False):
|
||||||
|
"""
|
||||||
|
Run evaluation code
|
||||||
|
:param model: PT model
|
||||||
|
:param dataloaders: list of PT dataloaders
|
||||||
|
:param max_batches: Scalar
|
||||||
|
:param test: boolean
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
# enable eval mode
|
||||||
|
model.zero_grad()
|
||||||
|
model.eval()
|
||||||
|
|
||||||
|
# copy properties for forward overrides
|
||||||
|
self.copy_trainer_model_properties(model)
|
||||||
|
|
||||||
|
# disable gradients to save memory
|
||||||
|
torch.set_grad_enabled(False)
|
||||||
|
|
||||||
|
# bookkeeping
|
||||||
|
outputs = []
|
||||||
|
|
||||||
|
# run training
|
||||||
|
for dataloader_idx, dataloader in enumerate(dataloaders):
|
||||||
|
dl_outputs = []
|
||||||
|
for batch_idx, batch in enumerate(dataloader):
|
||||||
|
|
||||||
|
if batch is None: # pragma: no cover
|
||||||
|
continue
|
||||||
|
|
||||||
|
# stop short when on fast_dev_run (sets max_batch=1)
|
||||||
|
if batch_idx >= max_batches:
|
||||||
|
break
|
||||||
|
|
||||||
|
# -----------------
|
||||||
|
# RUN EVALUATION STEP
|
||||||
|
# -----------------
|
||||||
|
output = self.evaluation_forward(model,
|
||||||
|
batch,
|
||||||
|
batch_idx,
|
||||||
|
dataloader_idx,
|
||||||
|
test)
|
||||||
|
|
||||||
|
# track outputs for collation
|
||||||
|
dl_outputs.append(output)
|
||||||
|
|
||||||
|
# batch done
|
||||||
|
if test:
|
||||||
|
self.test_progress_bar.update(1)
|
||||||
|
else:
|
||||||
|
self.val_progress_bar.update(1)
|
||||||
|
self.main_progress_bar.update(1)
|
||||||
|
outputs.append(dl_outputs)
|
||||||
|
|
||||||
|
eval_results = {}
|
||||||
|
|
||||||
|
# with a single dataloader don't pass an array
|
||||||
|
if len(dataloaders) == 1:
|
||||||
|
outputs = outputs[0]
|
||||||
|
|
||||||
|
# give model a chance to do something with the outputs (and method defined)
|
||||||
|
model = self.get_model()
|
||||||
|
if test and self.is_overriden('test_end'):
|
||||||
|
eval_results = model.test_end(outputs)
|
||||||
|
elif self.is_overriden('validation_end'):
|
||||||
|
eval_results = model.validation_end(outputs)
|
||||||
|
|
||||||
|
# enable train mode again
|
||||||
|
model.train()
|
||||||
|
|
||||||
|
# enable gradients to save memory
|
||||||
|
torch.set_grad_enabled(True)
|
||||||
|
|
||||||
|
return eval_results
|
||||||
|
|
||||||
|
def run_evaluation(self, test=False):
|
||||||
|
# when testing make sure user defined a test step
|
||||||
|
can_run_test_step = False
|
||||||
|
if test:
|
||||||
|
can_run_test_step = self.is_overriden('test_step') and self.is_overriden('test_end')
|
||||||
|
if not can_run_test_step:
|
||||||
|
m = '''You called .test() without defining a test step or test_end.
|
||||||
|
Please define and try again'''
|
||||||
|
raise MisconfigurationException(m)
|
||||||
|
|
||||||
|
# validate only if model has validation_step defined
|
||||||
|
# test only if test_step or validation_step are defined
|
||||||
|
run_val_step = self.is_overriden('validation_step')
|
||||||
|
|
||||||
|
if run_val_step or can_run_test_step:
|
||||||
|
|
||||||
|
# hook
|
||||||
|
model = self.get_model()
|
||||||
|
model.on_pre_performance_check()
|
||||||
|
|
||||||
|
# select dataloaders
|
||||||
|
if test:
|
||||||
|
dataloaders = self.get_test_dataloaders()
|
||||||
|
max_batches = self.nb_test_batches
|
||||||
|
else:
|
||||||
|
# val
|
||||||
|
dataloaders = self.get_val_dataloaders()
|
||||||
|
max_batches = self.nb_val_batches
|
||||||
|
|
||||||
|
# cap max batches to 1 when using fast_dev_run
|
||||||
|
if self.fast_dev_run:
|
||||||
|
max_batches = 1
|
||||||
|
|
||||||
|
# init validation or test progress bar
|
||||||
|
# main progress bar will already be closed when testing so initial position is free
|
||||||
|
position = 2 * self.process_position + (not test)
|
||||||
|
desc = 'Testing' if test else 'Validating'
|
||||||
|
pbar = tqdm.tqdm(desc=desc, total=max_batches, leave=test, position=position,
|
||||||
|
disable=not self.show_progress_bar, dynamic_ncols=True,
|
||||||
|
unit='batch')
|
||||||
|
setattr(self, f'{"test" if test else "val"}_progress_bar', pbar)
|
||||||
|
|
||||||
|
# run evaluation
|
||||||
|
eval_results = self.evaluate(self.model,
|
||||||
|
dataloaders,
|
||||||
|
max_batches,
|
||||||
|
test)
|
||||||
|
_, prog_bar_metrics, log_metrics, callback_metrics, _ = self.process_output(
|
||||||
|
eval_results)
|
||||||
|
|
||||||
|
# add metrics to prog bar
|
||||||
|
self.add_tqdm_metrics(prog_bar_metrics)
|
||||||
|
|
||||||
|
# log metrics
|
||||||
|
self.log_metrics(log_metrics, {})
|
||||||
|
|
||||||
|
# track metrics for callbacks
|
||||||
|
self.callback_metrics = callback_metrics
|
||||||
|
|
||||||
|
# hook
|
||||||
|
model.on_post_performance_check()
|
||||||
|
|
||||||
|
# add model specific metrics
|
||||||
|
tqdm_metrics = self.training_tqdm_dict
|
||||||
|
if not test:
|
||||||
|
self.main_progress_bar.set_postfix(**tqdm_metrics)
|
||||||
|
|
||||||
|
# close progress bar
|
||||||
|
if test:
|
||||||
|
self.test_progress_bar.close()
|
||||||
|
else:
|
||||||
|
self.val_progress_bar.close()
|
||||||
|
|
||||||
|
# model checkpointing
|
||||||
|
if self.proc_rank == 0 and self.checkpoint_callback is not None and not test:
|
||||||
|
self.checkpoint_callback.on_epoch_end(epoch=self.current_epoch,
|
||||||
|
logs=self.callback_metrics)
|
||||||
|
|
||||||
|
def evaluation_forward(self, model, batch, batch_idx, dataloader_idx, test=False):
|
||||||
|
# make dataloader_idx arg in validation_step optional
|
||||||
|
args = [batch, batch_idx]
|
||||||
|
|
||||||
|
if test and len(self.get_test_dataloaders()) > 1:
|
||||||
|
args.append(dataloader_idx)
|
||||||
|
|
||||||
|
elif not test and len(self.get_val_dataloaders()) > 1:
|
||||||
|
args.append(dataloader_idx)
|
||||||
|
|
||||||
|
# handle DP, DDP forward
|
||||||
|
if self.use_ddp or self.use_dp or self.use_ddp2:
|
||||||
|
output = model(*args)
|
||||||
|
return output
|
||||||
|
|
||||||
|
# single GPU
|
||||||
|
if self.single_gpu:
|
||||||
|
# for single GPU put inputs on gpu manually
|
||||||
|
root_gpu = 0
|
||||||
|
if type(self.data_parallel_device_ids) is list:
|
||||||
|
root_gpu = self.data_parallel_device_ids[0]
|
||||||
|
batch = self.transfer_batch_to_gpu(batch, root_gpu)
|
||||||
|
args[0] = batch
|
||||||
|
|
||||||
|
# CPU
|
||||||
|
if test:
|
||||||
|
output = model.test_step(*args)
|
||||||
|
else:
|
||||||
|
output = model.validation_step(*args)
|
||||||
|
|
||||||
|
return output
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
import torch
|
||||||
|
|
||||||
|
from pytorch_lightning.root_module import memory
|
||||||
|
|
||||||
|
|
||||||
|
class TrainerLoggingMixin(object):
|
||||||
|
|
||||||
|
def log_metrics(self, metrics, grad_norm_dic):
|
||||||
|
"""
|
||||||
|
Logs the metric dict passed in
|
||||||
|
:param metrics:
|
||||||
|
:param grad_norm_dic:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
# added metrics by Lightning for convenience
|
||||||
|
metrics['epoch'] = self.current_epoch
|
||||||
|
|
||||||
|
# add gpu memory
|
||||||
|
if self.on_gpu and self.log_gpu_memory:
|
||||||
|
mem_map = memory.get_memory_profile(self.log_gpu_memory)
|
||||||
|
metrics.update(mem_map)
|
||||||
|
|
||||||
|
# add norms
|
||||||
|
metrics.update(grad_norm_dic)
|
||||||
|
|
||||||
|
# turn all tensors to scalars
|
||||||
|
scalar_metrics = self.metrics_to_scalars(metrics)
|
||||||
|
|
||||||
|
# log actual metrics
|
||||||
|
if self.proc_rank == 0 and self.logger is not None:
|
||||||
|
self.logger.log_metrics(scalar_metrics, step_num=self.global_step)
|
||||||
|
self.logger.save()
|
||||||
|
|
||||||
|
def add_tqdm_metrics(self, metrics):
|
||||||
|
for k, v in metrics.items():
|
||||||
|
if type(v) is torch.Tensor:
|
||||||
|
v = v.item()
|
||||||
|
|
||||||
|
self.tqdm_metrics[k] = v
|
||||||
|
|
||||||
|
def metrics_to_scalars(self, metrics):
|
||||||
|
new_metrics = {}
|
||||||
|
for k, v in metrics.items():
|
||||||
|
if isinstance(v, torch.Tensor):
|
||||||
|
v = v.item()
|
||||||
|
|
||||||
|
if type(v) is dict:
|
||||||
|
v = self.metrics_to_scalars(v)
|
||||||
|
|
||||||
|
new_metrics[k] = v
|
||||||
|
|
||||||
|
return new_metrics
|
||||||
|
|
||||||
|
def process_output(self, output, train=False):
|
||||||
|
"""
|
||||||
|
Reduces output according to the training mode.
|
||||||
|
Separates loss from logging and tqdm metrics
|
||||||
|
:param output:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
# ---------------
|
||||||
|
# EXTRACT CALLBACK KEYS
|
||||||
|
# ---------------
|
||||||
|
# all keys not progress_bar or log are candidates for callbacks
|
||||||
|
callback_metrics = {}
|
||||||
|
for k, v in output.items():
|
||||||
|
if k not in ['progress_bar', 'log', 'hiddens']:
|
||||||
|
callback_metrics[k] = v
|
||||||
|
|
||||||
|
if train and (self.use_dp or self.use_ddp2):
|
||||||
|
nb_gpus = self.num_gpus
|
||||||
|
callback_metrics = self.reduce_distributed_output(callback_metrics, nb_gpus)
|
||||||
|
|
||||||
|
for k, v in callback_metrics.items():
|
||||||
|
callback_metrics[k] = v.item()
|
||||||
|
|
||||||
|
# ---------------
|
||||||
|
# EXTRACT PROGRESS BAR KEYS
|
||||||
|
# ---------------
|
||||||
|
try:
|
||||||
|
progress_output = output['progress_bar']
|
||||||
|
|
||||||
|
# reduce progress metrics for tqdm when using dp
|
||||||
|
if train and (self.use_dp or self.use_ddp2):
|
||||||
|
nb_gpus = self.num_gpus
|
||||||
|
progress_output = self.reduce_distributed_output(progress_output, nb_gpus)
|
||||||
|
|
||||||
|
progress_bar_metrics = progress_output
|
||||||
|
except Exception:
|
||||||
|
progress_bar_metrics = {}
|
||||||
|
|
||||||
|
# ---------------
|
||||||
|
# EXTRACT LOGGING KEYS
|
||||||
|
# ---------------
|
||||||
|
# extract metrics to log to experiment
|
||||||
|
try:
|
||||||
|
log_output = output['log']
|
||||||
|
|
||||||
|
# reduce progress metrics for tqdm when using dp
|
||||||
|
if train and (self.use_dp or self.use_ddp2):
|
||||||
|
nb_gpus = self.num_gpus
|
||||||
|
log_output = self.reduce_distributed_output(log_output, nb_gpus)
|
||||||
|
|
||||||
|
log_metrics = log_output
|
||||||
|
except Exception:
|
||||||
|
log_metrics = {}
|
||||||
|
|
||||||
|
# ---------------
|
||||||
|
# EXTRACT LOSS
|
||||||
|
# ---------------
|
||||||
|
# if output dict doesn't have the keyword loss
|
||||||
|
# then assume the output=loss if scalar
|
||||||
|
loss = None
|
||||||
|
if train:
|
||||||
|
try:
|
||||||
|
loss = output['loss']
|
||||||
|
except Exception:
|
||||||
|
if type(output) is torch.Tensor:
|
||||||
|
loss = output
|
||||||
|
else:
|
||||||
|
raise RuntimeError(
|
||||||
|
'No `loss` value in the dictionary returned from `model.training_step()`.'
|
||||||
|
)
|
||||||
|
|
||||||
|
# when using dp need to reduce the loss
|
||||||
|
if self.use_dp or self.use_ddp2:
|
||||||
|
loss = self.reduce_distributed_output(loss, self.num_gpus)
|
||||||
|
|
||||||
|
# ---------------
|
||||||
|
# EXTRACT HIDDEN
|
||||||
|
# ---------------
|
||||||
|
hiddens = output.get('hiddens')
|
||||||
|
|
||||||
|
# use every metric passed in as a candidate for callback
|
||||||
|
callback_metrics.update(progress_bar_metrics)
|
||||||
|
callback_metrics.update(log_metrics)
|
||||||
|
|
||||||
|
# convert tensors to numpy
|
||||||
|
for k, v in callback_metrics.items():
|
||||||
|
if isinstance(v, torch.Tensor):
|
||||||
|
callback_metrics[k] = v.item()
|
||||||
|
|
||||||
|
return loss, progress_bar_metrics, log_metrics, callback_metrics, hiddens
|
||||||
|
|
||||||
|
def reduce_distributed_output(self, output, nb_gpus):
|
||||||
|
if nb_gpus <= 1:
|
||||||
|
return output
|
||||||
|
|
||||||
|
# when using DP, we get one output per gpu
|
||||||
|
# average outputs and return
|
||||||
|
if type(output) is torch.Tensor:
|
||||||
|
return output.mean()
|
||||||
|
|
||||||
|
for k, v in output.items():
|
||||||
|
# recurse on nested dics
|
||||||
|
if isinstance(output[k], dict):
|
||||||
|
output[k] = self.reduce_distributed_output(output[k], nb_gpus)
|
||||||
|
|
||||||
|
# do nothing when there's a scalar
|
||||||
|
elif isinstance(output[k], torch.Tensor) and output[k].dim() == 0:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# reduce only metrics that have the same nb of gpus
|
||||||
|
elif output[k].size(0) == nb_gpus:
|
||||||
|
reduced = torch.mean(output[k])
|
||||||
|
output[k] = reduced
|
||||||
|
return output
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
from pytorch_lightning.root_module.root_module import LightningModule
|
||||||
|
|
||||||
|
|
||||||
|
class TrainerModelHooksMixin(object):
|
||||||
|
|
||||||
|
def is_function_implemented(self, f_name):
|
||||||
|
model = self.get_model()
|
||||||
|
f_op = getattr(model, f_name, None)
|
||||||
|
return callable(f_op)
|
||||||
|
|
||||||
|
def is_overriden(self, f_name):
|
||||||
|
model = self.get_model()
|
||||||
|
super_object = LightningModule
|
||||||
|
|
||||||
|
# when code pointers are different, it was overriden
|
||||||
|
is_overriden = getattr(model, f_name).__code__ is not getattr(super_object, f_name).__code__
|
||||||
|
return is_overriden
|
||||||
@@ -0,0 +1,309 @@
|
|||||||
|
import numpy as np
|
||||||
|
import tqdm
|
||||||
|
|
||||||
|
try:
|
||||||
|
from apex import amp
|
||||||
|
|
||||||
|
APEX_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
APEX_AVAILABLE = False
|
||||||
|
|
||||||
|
|
||||||
|
class TrainerTrainLoopMixin(object):
|
||||||
|
|
||||||
|
def train(self):
|
||||||
|
# run all epochs
|
||||||
|
for epoch_nb in range(self.current_epoch, self.max_nb_epochs):
|
||||||
|
# set seed for distributed sampler (enables shuffling for each epoch)
|
||||||
|
if self.use_ddp and hasattr(self.get_train_dataloader().sampler, 'set_epoch'):
|
||||||
|
self.get_train_dataloader().sampler.set_epoch(epoch_nb)
|
||||||
|
|
||||||
|
# get model
|
||||||
|
model = self.get_model()
|
||||||
|
|
||||||
|
# update training progress in trainer and model
|
||||||
|
model.current_epoch = epoch_nb
|
||||||
|
self.current_epoch = epoch_nb
|
||||||
|
|
||||||
|
# val can be checked multiple times in epoch
|
||||||
|
is_val_epoch = (self.current_epoch + 1) % self.check_val_every_n_epoch == 0
|
||||||
|
val_checks_per_epoch = self.nb_training_batches // self.val_check_batch
|
||||||
|
val_checks_per_epoch = val_checks_per_epoch if is_val_epoch else 0
|
||||||
|
|
||||||
|
# total batches includes multiple val checks
|
||||||
|
self.total_batches = (self.nb_training_batches +
|
||||||
|
self.nb_val_batches * val_checks_per_epoch)
|
||||||
|
self.batch_loss_value = 0 # accumulated grads
|
||||||
|
|
||||||
|
if self.fast_dev_run:
|
||||||
|
# limit the number of batches to 2 (1 train and 1 val) in fast_dev_run
|
||||||
|
nb_iterations = 2
|
||||||
|
elif self.is_iterable_train_dataloader:
|
||||||
|
# for iterable train loader, the progress bar never ends
|
||||||
|
nb_iterations = None
|
||||||
|
else:
|
||||||
|
nb_iterations = self.total_batches
|
||||||
|
|
||||||
|
# reset progress bar
|
||||||
|
# .reset() doesn't work on disabled progress bar so we should check
|
||||||
|
if not self.main_progress_bar.disable:
|
||||||
|
self.main_progress_bar.reset(nb_iterations)
|
||||||
|
desc = f'Epoch {epoch_nb + 1}' if not self.is_iterable_train_dataloader else ''
|
||||||
|
self.main_progress_bar.set_description(desc)
|
||||||
|
|
||||||
|
# changing gradient according accumulation_scheduler
|
||||||
|
self.accumulation_scheduler.on_epoch_begin(epoch_nb, self)
|
||||||
|
|
||||||
|
# -----------------
|
||||||
|
# RUN TNG EPOCH
|
||||||
|
# -----------------
|
||||||
|
self.run_training_epoch()
|
||||||
|
|
||||||
|
# update LR schedulers
|
||||||
|
if self.lr_schedulers is not None:
|
||||||
|
for lr_scheduler in self.lr_schedulers:
|
||||||
|
lr_scheduler.step(self.current_epoch)
|
||||||
|
|
||||||
|
# early stopping
|
||||||
|
met_min_epochs = epoch_nb > self.min_nb_epochs
|
||||||
|
if self.enable_early_stop and (met_min_epochs or self.fast_dev_run):
|
||||||
|
should_stop = self.early_stop_callback.on_epoch_end(epoch=epoch_nb,
|
||||||
|
logs=self.callback_metrics)
|
||||||
|
# stop training
|
||||||
|
stop = should_stop and met_min_epochs
|
||||||
|
if stop:
|
||||||
|
self.main_progress_bar.close()
|
||||||
|
return
|
||||||
|
|
||||||
|
self.main_progress_bar.close()
|
||||||
|
|
||||||
|
if self.logger is not None:
|
||||||
|
self.logger.finalize("success")
|
||||||
|
|
||||||
|
def run_training_epoch(self):
|
||||||
|
# before epoch hook
|
||||||
|
if self.is_function_implemented('on_epoch_start'):
|
||||||
|
model = self.get_model()
|
||||||
|
model.on_epoch_start()
|
||||||
|
|
||||||
|
# run epoch
|
||||||
|
for batch_nb, batch in enumerate(self.get_train_dataloader()):
|
||||||
|
self.batch_nb = batch_nb
|
||||||
|
|
||||||
|
model = self.get_model()
|
||||||
|
model.global_step = self.global_step
|
||||||
|
|
||||||
|
# ---------------
|
||||||
|
# RUN TRAIN STEP
|
||||||
|
# ---------------
|
||||||
|
output = self.run_training_batch(batch, batch_nb)
|
||||||
|
batch_result, grad_norm_dic, batch_step_metrics = output
|
||||||
|
|
||||||
|
# when returning -1 from train_step, we end epoch early
|
||||||
|
early_stop_epoch = batch_result == -1
|
||||||
|
|
||||||
|
# ---------------
|
||||||
|
# RUN VAL STEP
|
||||||
|
# ---------------
|
||||||
|
is_val_check_batch = (batch_nb + 1) % self.val_check_batch == 0
|
||||||
|
can_check_epoch = (self.current_epoch + 1) % self.check_val_every_n_epoch == 0
|
||||||
|
should_check_val = ((is_val_check_batch or early_stop_epoch) and can_check_epoch)
|
||||||
|
|
||||||
|
# fast_dev_run always forces val checking after train batch
|
||||||
|
if self.fast_dev_run or should_check_val:
|
||||||
|
self.run_evaluation(test=self.testing)
|
||||||
|
|
||||||
|
# when logs should be saved
|
||||||
|
should_save_log = (batch_nb + 1) % self.log_save_interval == 0 or early_stop_epoch
|
||||||
|
if should_save_log or self.fast_dev_run:
|
||||||
|
if self.proc_rank == 0 and self.logger is not None:
|
||||||
|
self.logger.save()
|
||||||
|
|
||||||
|
# when metrics should be logged
|
||||||
|
should_log_metrics = batch_nb % self.row_log_interval == 0 or early_stop_epoch
|
||||||
|
if should_log_metrics or self.fast_dev_run:
|
||||||
|
# logs user requested information to logger
|
||||||
|
self.log_metrics(batch_step_metrics, grad_norm_dic)
|
||||||
|
|
||||||
|
self.global_step += 1
|
||||||
|
self.total_batch_nb += 1
|
||||||
|
|
||||||
|
# end epoch early
|
||||||
|
# stop when the flag is changed or we've gone past the amount
|
||||||
|
# requested in the batches
|
||||||
|
if early_stop_epoch or self.fast_dev_run:
|
||||||
|
break
|
||||||
|
|
||||||
|
# stop epoch if we limited nb batches
|
||||||
|
met_batch_limit = batch_nb >= self.nb_training_batches
|
||||||
|
if met_batch_limit:
|
||||||
|
break
|
||||||
|
|
||||||
|
# epoch end hook
|
||||||
|
if self.is_function_implemented('on_epoch_end'):
|
||||||
|
model = self.get_model()
|
||||||
|
model.on_epoch_end()
|
||||||
|
|
||||||
|
def run_training_batch(self, batch, batch_nb):
|
||||||
|
# track grad norms
|
||||||
|
grad_norm_dic = {}
|
||||||
|
|
||||||
|
# track all metrics for callbacks
|
||||||
|
all_callback_metrics = []
|
||||||
|
|
||||||
|
# track metrics to log
|
||||||
|
all_log_metrics = []
|
||||||
|
|
||||||
|
if batch is None:
|
||||||
|
return 0, grad_norm_dic
|
||||||
|
|
||||||
|
# hook
|
||||||
|
if self.is_function_implemented('on_batch_start'):
|
||||||
|
model_ref = self.get_model()
|
||||||
|
response = model_ref.on_batch_start(batch)
|
||||||
|
|
||||||
|
if response == -1:
|
||||||
|
return -1, grad_norm_dic
|
||||||
|
|
||||||
|
splits = [batch]
|
||||||
|
if self.truncated_bptt_steps is not None:
|
||||||
|
model_ref = self.get_model()
|
||||||
|
splits = model_ref.tbptt_split_batch(batch, self.truncated_bptt_steps)
|
||||||
|
|
||||||
|
self.hiddens = None
|
||||||
|
for split_nb, split_batch in enumerate(splits):
|
||||||
|
self.split_nb = split_nb
|
||||||
|
|
||||||
|
# call training_step once per optimizer
|
||||||
|
for opt_idx, optimizer in enumerate(self.optimizers):
|
||||||
|
|
||||||
|
# wrap the forward step in a closure so second order methods work
|
||||||
|
def optimizer_closure():
|
||||||
|
# forward pass
|
||||||
|
output = self.training_forward(
|
||||||
|
split_batch, batch_nb, opt_idx, self.hiddens)
|
||||||
|
|
||||||
|
closure_loss = output[0]
|
||||||
|
progress_bar_metrics = output[1]
|
||||||
|
log_metrics = output[2]
|
||||||
|
callback_metrics = output[3]
|
||||||
|
self.hiddens = output[4]
|
||||||
|
|
||||||
|
# accumulate loss
|
||||||
|
# (if accumulate_grad_batches = 1 no effect)
|
||||||
|
closure_loss = closure_loss / self.accumulate_grad_batches
|
||||||
|
|
||||||
|
# backward pass
|
||||||
|
model_ref = self.get_model()
|
||||||
|
model_ref.backward(self.use_amp, closure_loss, optimizer)
|
||||||
|
|
||||||
|
# track metrics for callbacks
|
||||||
|
all_callback_metrics.append(callback_metrics)
|
||||||
|
|
||||||
|
# track progress bar metrics
|
||||||
|
self.add_tqdm_metrics(progress_bar_metrics)
|
||||||
|
all_log_metrics.append(log_metrics)
|
||||||
|
|
||||||
|
# insert after step hook
|
||||||
|
if self.is_function_implemented('on_after_backward'):
|
||||||
|
model_ref = self.get_model()
|
||||||
|
model_ref.on_after_backward()
|
||||||
|
|
||||||
|
return closure_loss
|
||||||
|
|
||||||
|
# calculate loss
|
||||||
|
loss = optimizer_closure()
|
||||||
|
|
||||||
|
# nan grads
|
||||||
|
if self.print_nan_grads:
|
||||||
|
self.print_nan_gradients()
|
||||||
|
|
||||||
|
# track total loss for logging (avoid mem leaks)
|
||||||
|
self.batch_loss_value += loss.item()
|
||||||
|
|
||||||
|
# gradient update with accumulated gradients
|
||||||
|
if (self.batch_nb + 1) % self.accumulate_grad_batches == 0:
|
||||||
|
|
||||||
|
# track gradient norms when requested
|
||||||
|
if batch_nb % self.row_log_interval == 0:
|
||||||
|
if self.track_grad_norm > 0:
|
||||||
|
model = self.get_model()
|
||||||
|
grad_norm_dic = model.grad_norm(
|
||||||
|
self.track_grad_norm)
|
||||||
|
|
||||||
|
# clip gradients
|
||||||
|
self.clip_gradients()
|
||||||
|
|
||||||
|
# calls .step(), .zero_grad()
|
||||||
|
# override function to modify this behavior
|
||||||
|
model = self.get_model()
|
||||||
|
model.optimizer_step(self.current_epoch, batch_nb,
|
||||||
|
optimizer, opt_idx, optimizer_closure)
|
||||||
|
|
||||||
|
# calculate running loss for display
|
||||||
|
self.running_loss.append(self.batch_loss_value)
|
||||||
|
self.batch_loss_value = 0
|
||||||
|
self.avg_loss = np.mean(self.running_loss[-100:])
|
||||||
|
|
||||||
|
# activate batch end hook
|
||||||
|
if self.is_function_implemented('on_batch_end'):
|
||||||
|
model = self.get_model()
|
||||||
|
model.on_batch_end()
|
||||||
|
|
||||||
|
# update progress bar
|
||||||
|
self.main_progress_bar.update(1)
|
||||||
|
self.main_progress_bar.set_postfix(**self.training_tqdm_dict)
|
||||||
|
|
||||||
|
# collapse all metrics into one dict
|
||||||
|
all_log_metrics = {k: v for d in all_log_metrics for k, v in d.items()}
|
||||||
|
|
||||||
|
# track all metrics for callbacks
|
||||||
|
self.callback_metrics = {k: v for d in all_callback_metrics for k, v in d.items()}
|
||||||
|
|
||||||
|
return 0, grad_norm_dic, all_log_metrics
|
||||||
|
|
||||||
|
def training_forward(self, batch, batch_nb, opt_idx, hiddens):
|
||||||
|
"""
|
||||||
|
Handle forward for each training case (distributed, single gpu, etc...)
|
||||||
|
:param batch:
|
||||||
|
:param batch_nb:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
# ---------------
|
||||||
|
# FORWARD
|
||||||
|
# ---------------
|
||||||
|
# enable not needing to add opt_idx to training_step
|
||||||
|
args = [batch, batch_nb]
|
||||||
|
if len(self.optimizers) > 1:
|
||||||
|
args.append(opt_idx)
|
||||||
|
|
||||||
|
# pass hiddens if using tbptt
|
||||||
|
if self.truncated_bptt_steps is not None:
|
||||||
|
args.append(hiddens)
|
||||||
|
|
||||||
|
# distributed forward
|
||||||
|
if self.use_ddp or self.use_ddp2 or self.use_dp:
|
||||||
|
output = self.model(*args)
|
||||||
|
|
||||||
|
# single GPU forward
|
||||||
|
elif self.single_gpu:
|
||||||
|
gpu_id = 0
|
||||||
|
if type(self.data_parallel_device_ids) is list:
|
||||||
|
gpu_id = self.data_parallel_device_ids[0]
|
||||||
|
batch = self.transfer_batch_to_gpu(batch, gpu_id)
|
||||||
|
args[0] = batch
|
||||||
|
output = self.model.training_step(*args)
|
||||||
|
|
||||||
|
# CPU forward
|
||||||
|
else:
|
||||||
|
output = self.model.training_step(*args)
|
||||||
|
|
||||||
|
# allow any mode to define training_end
|
||||||
|
if self.is_overriden('training_end'):
|
||||||
|
model_ref = self.get_model()
|
||||||
|
output = model_ref.training_end(output)
|
||||||
|
|
||||||
|
# format and reduce outputs accordingly
|
||||||
|
output = self.process_output(output, train=True)
|
||||||
|
|
||||||
|
return output
|
||||||
+151
-1140
File diff suppressed because it is too large
Load Diff
@@ -1,18 +1,20 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import signal
|
import signal
|
||||||
import pdb
|
import warnings
|
||||||
from subprocess import call
|
from subprocess import call
|
||||||
|
import logging
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
import torch.distributed as dist
|
||||||
|
|
||||||
from pytorch_lightning.pt_overrides.override_data_parallel import (
|
from pytorch_lightning.pt_overrides.override_data_parallel import (
|
||||||
LightningDistributedDataParallel, LightningDataParallel)
|
LightningDistributedDataParallel, LightningDataParallel)
|
||||||
|
|
||||||
|
|
||||||
class TrainerIO(object):
|
class TrainerIOMixin(object):
|
||||||
|
|
||||||
def __get_model(self):
|
def get_model(self):
|
||||||
is_dp_module = isinstance(self.model, (LightningDistributedDataParallel,
|
is_dp_module = isinstance(self.model, (LightningDistributedDataParallel,
|
||||||
LightningDataParallel))
|
LightningDataParallel))
|
||||||
model = self.model.module if is_dp_module else self.model
|
model = self.model.module if is_dp_module else self.model
|
||||||
@@ -24,22 +26,44 @@ class TrainerIO(object):
|
|||||||
def restore_weights(self, model):
|
def restore_weights(self, model):
|
||||||
"""
|
"""
|
||||||
To restore weights we have two cases.
|
To restore weights we have two cases.
|
||||||
First, if we use the same experiment version, then restore the latest ckpt.
|
First, attempt to restore hpc weights. If successful, don't restore
|
||||||
AFTER that, if we find weights from hpc checkpoint, then restore that.
|
other weights.
|
||||||
|
|
||||||
|
Otherwise, try to restore actual weights
|
||||||
:param model:
|
:param model:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
# restore weights if same exp version
|
# clear cache before restore
|
||||||
self.restore_state_if_checkpoint_exists(model)
|
if self.on_gpu:
|
||||||
|
torch.cuda.empty_cache()
|
||||||
|
|
||||||
# if script called from hpc resubmit, load weights
|
# if script called from hpc resubmit, load weights
|
||||||
self.restore_hpc_weights_if_needed(model)
|
did_restore_hpc_weights = self.restore_hpc_weights_if_needed(model)
|
||||||
|
|
||||||
|
# clear cache after restore
|
||||||
|
if self.on_gpu:
|
||||||
|
torch.cuda.empty_cache()
|
||||||
|
|
||||||
|
if not did_restore_hpc_weights:
|
||||||
|
# restore weights if same exp version
|
||||||
|
self.restore_state_if_checkpoint_exists(model)
|
||||||
|
|
||||||
|
# wait for all models to restore weights
|
||||||
|
if self.use_ddp or self.use_ddp2:
|
||||||
|
# wait for all processes to catch up
|
||||||
|
dist.barrier()
|
||||||
|
|
||||||
|
# clear cache after restore
|
||||||
|
if self.on_gpu:
|
||||||
|
torch.cuda.empty_cache()
|
||||||
|
|
||||||
def restore_state_if_checkpoint_exists(self, model):
|
def restore_state_if_checkpoint_exists(self, model):
|
||||||
|
did_restore = False
|
||||||
|
|
||||||
# do nothing if there's not dir or callback
|
# do nothing if there's not dir or callback
|
||||||
no_ckpt_callback = self.checkpoint_callback is None
|
no_ckpt_callback = (self.checkpoint_callback is None) or (not self.checkpoint_callback)
|
||||||
if no_ckpt_callback or not os.path.exists(self.checkpoint_callback.filepath):
|
if no_ckpt_callback or not os.path.exists(self.checkpoint_callback.filepath):
|
||||||
return
|
return did_restore
|
||||||
|
|
||||||
# restore trainer state and model if there is a weight for this experiment
|
# restore trainer state and model if there is a weight for this experiment
|
||||||
last_epoch = -1
|
last_epoch = -1
|
||||||
@@ -64,7 +88,10 @@ class TrainerIO(object):
|
|||||||
if last_ckpt_name is not None:
|
if last_ckpt_name is not None:
|
||||||
last_ckpt_path = os.path.join(self.checkpoint_callback.filepath, last_ckpt_name)
|
last_ckpt_path = os.path.join(self.checkpoint_callback.filepath, last_ckpt_name)
|
||||||
self.restore(last_ckpt_path, self.on_gpu)
|
self.restore(last_ckpt_path, self.on_gpu)
|
||||||
print(f'model and trainer restored from checkpoint: {last_ckpt_path}')
|
logging.info(f'model and trainer restored from checkpoint: {last_ckpt_path}')
|
||||||
|
did_restore = True
|
||||||
|
|
||||||
|
return did_restore
|
||||||
|
|
||||||
# --------------------
|
# --------------------
|
||||||
# HPC SIGNAL HANDLING
|
# HPC SIGNAL HANDLING
|
||||||
@@ -80,14 +107,14 @@ class TrainerIO(object):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
if on_slurm:
|
if on_slurm:
|
||||||
print('set slurm handle signals')
|
logging.info('set slurm handle signals')
|
||||||
signal.signal(signal.SIGUSR1, self.sig_handler)
|
signal.signal(signal.SIGUSR1, self.sig_handler)
|
||||||
signal.signal(signal.SIGTERM, self.term_handler)
|
signal.signal(signal.SIGTERM, self.term_handler)
|
||||||
|
|
||||||
def sig_handler(self, signum, frame):
|
def sig_handler(self, signum, frame):
|
||||||
if self.proc_rank == 0:
|
if self.proc_rank == 0:
|
||||||
# save weights
|
# save weights
|
||||||
print('handling SIGUSR1')
|
logging.info('handling SIGUSR1')
|
||||||
self.hpc_save(self.weights_save_path, self.logger)
|
self.hpc_save(self.weights_save_path, self.logger)
|
||||||
|
|
||||||
# find job id
|
# find job id
|
||||||
@@ -95,21 +122,21 @@ class TrainerIO(object):
|
|||||||
cmd = 'scontrol requeue {}'.format(job_id)
|
cmd = 'scontrol requeue {}'.format(job_id)
|
||||||
|
|
||||||
# requeue job
|
# requeue job
|
||||||
print('\nrequeing job {}...'.format(job_id))
|
logging.info('\nrequeing job {job_id}...')
|
||||||
result = call(cmd, shell=True)
|
result = call(cmd, shell=True)
|
||||||
|
|
||||||
# print result text
|
# print result text
|
||||||
if result == 0:
|
if result == 0:
|
||||||
print('requeued exp ', job_id)
|
logging.info('requeued exp {job_id}')
|
||||||
else:
|
else:
|
||||||
print('requeue failed...')
|
logging.info('requeue failed...')
|
||||||
|
|
||||||
# close experiment to avoid issues
|
# close experiment to avoid issues
|
||||||
self.logger.close()
|
self.logger.close()
|
||||||
|
|
||||||
def term_handler(self, signum, frame):
|
def term_handler(self, signum, frame):
|
||||||
# save
|
# save
|
||||||
print("bypassing sigterm")
|
logging.info("bypassing sigterm")
|
||||||
|
|
||||||
# --------------------
|
# --------------------
|
||||||
# MODEL SAVE CHECKPOINT
|
# MODEL SAVE CHECKPOINT
|
||||||
@@ -118,7 +145,13 @@ class TrainerIO(object):
|
|||||||
checkpoint = self.dump_checkpoint()
|
checkpoint = self.dump_checkpoint()
|
||||||
|
|
||||||
# do the actual save
|
# do the actual save
|
||||||
torch.save(checkpoint, filepath)
|
try:
|
||||||
|
torch.save(checkpoint, filepath)
|
||||||
|
except AttributeError:
|
||||||
|
if 'hparams' in checkpoint:
|
||||||
|
del checkpoint['hparams']
|
||||||
|
|
||||||
|
torch.save(checkpoint, filepath)
|
||||||
|
|
||||||
def restore(self, checkpoint_path, on_gpu):
|
def restore(self, checkpoint_path, on_gpu):
|
||||||
|
|
||||||
@@ -129,7 +162,7 @@ class TrainerIO(object):
|
|||||||
checkpoint = torch.load(checkpoint_path, map_location=lambda storage, loc: storage)
|
checkpoint = torch.load(checkpoint_path, map_location=lambda storage, loc: storage)
|
||||||
|
|
||||||
# load model state
|
# load model state
|
||||||
model = self.__get_model()
|
model = self.get_model()
|
||||||
|
|
||||||
# load the state_dict on the model automatically
|
# load the state_dict on the model automatically
|
||||||
model.load_state_dict(checkpoint['state_dict'])
|
model.load_state_dict(checkpoint['state_dict'])
|
||||||
@@ -146,10 +179,10 @@ class TrainerIO(object):
|
|||||||
'global_step': self.global_step
|
'global_step': self.global_step
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.checkpoint_callback is not None:
|
if self.checkpoint_callback is not None and self.checkpoint_callback is not False:
|
||||||
checkpoint['checkpoint_callback_best'] = self.checkpoint_callback.best
|
checkpoint['checkpoint_callback_best'] = self.checkpoint_callback.best
|
||||||
|
|
||||||
if self.early_stop_callback is not None:
|
if self.early_stop_callback is not None and self.checkpoint_callback is not False:
|
||||||
checkpoint['early_stop_callback_wait'] = self.early_stop_callback.wait
|
checkpoint['early_stop_callback_wait'] = self.early_stop_callback.wait
|
||||||
checkpoint['early_stop_callback_patience'] = self.early_stop_callback.patience
|
checkpoint['early_stop_callback_patience'] = self.early_stop_callback.patience
|
||||||
|
|
||||||
@@ -167,9 +200,16 @@ class TrainerIO(object):
|
|||||||
|
|
||||||
checkpoint['lr_schedulers'] = lr_schedulers
|
checkpoint['lr_schedulers'] = lr_schedulers
|
||||||
|
|
||||||
# add the state_dict from the model
|
# add the hparams and state_dict from the model
|
||||||
model = self.__get_model()
|
model = self.get_model()
|
||||||
checkpoint['state_dict'] = model.state_dict()
|
checkpoint['state_dict'] = model.state_dict()
|
||||||
|
if hasattr(model, "hparams"):
|
||||||
|
checkpoint['hparams'] = vars(model.hparams)
|
||||||
|
else:
|
||||||
|
warnings.warn(
|
||||||
|
"Did not find hyperparameters at model.hparams. Saving checkpoint without"
|
||||||
|
" hyperparameters"
|
||||||
|
)
|
||||||
|
|
||||||
# give the model a chance to add a few things
|
# give the model a chance to add a few things
|
||||||
model.on_save_checkpoint(checkpoint)
|
model.on_save_checkpoint(checkpoint)
|
||||||
@@ -185,6 +225,8 @@ class TrainerIO(object):
|
|||||||
:param model:
|
:param model:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
did_restore = False
|
||||||
|
|
||||||
# look for hpc weights
|
# look for hpc weights
|
||||||
folderpath = self.weights_save_path
|
folderpath = self.weights_save_path
|
||||||
if os.path.exists(folderpath):
|
if os.path.exists(folderpath):
|
||||||
@@ -194,6 +236,8 @@ class TrainerIO(object):
|
|||||||
# if hpc weights exist restore model
|
# if hpc weights exist restore model
|
||||||
if len(hpc_weight_paths) > 0:
|
if len(hpc_weight_paths) > 0:
|
||||||
self.hpc_load(folderpath, self.on_gpu)
|
self.hpc_load(folderpath, self.on_gpu)
|
||||||
|
did_restore = True
|
||||||
|
return did_restore
|
||||||
|
|
||||||
def restore_training_state(self, checkpoint):
|
def restore_training_state(self, checkpoint):
|
||||||
"""
|
"""
|
||||||
@@ -202,10 +246,10 @@ class TrainerIO(object):
|
|||||||
:param checkpoint:
|
:param checkpoint:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if self.checkpoint_callback is not None:
|
if self.checkpoint_callback is not None and self.checkpoint_callback is not False:
|
||||||
self.checkpoint_callback.best = checkpoint['checkpoint_callback_best']
|
self.checkpoint_callback.best = checkpoint['checkpoint_callback_best']
|
||||||
|
|
||||||
if self.early_stop_callback is not None:
|
if self.early_stop_callback is not None and self.early_stop_callback is not False:
|
||||||
self.early_stop_callback.wait = checkpoint['early_stop_callback_wait']
|
self.early_stop_callback.wait = checkpoint['early_stop_callback_wait']
|
||||||
self.early_stop_callback.patience = checkpoint['early_stop_callback_patience']
|
self.early_stop_callback.patience = checkpoint['early_stop_callback_patience']
|
||||||
|
|
||||||
@@ -247,13 +291,20 @@ class TrainerIO(object):
|
|||||||
filepath = '{}/hpc_ckpt_{}.ckpt'.format(folderpath, ckpt_number)
|
filepath = '{}/hpc_ckpt_{}.ckpt'.format(folderpath, ckpt_number)
|
||||||
|
|
||||||
# give model a chance to do something on hpc_save
|
# give model a chance to do something on hpc_save
|
||||||
model = self.__get_model()
|
model = self.get_model()
|
||||||
checkpoint = self.dump_checkpoint()
|
checkpoint = self.dump_checkpoint()
|
||||||
|
|
||||||
model.on_hpc_save(checkpoint)
|
model.on_hpc_save(checkpoint)
|
||||||
|
|
||||||
# do the actual save
|
# do the actual save
|
||||||
torch.save(checkpoint, filepath)
|
# TODO: fix for anything with multiprocess DP, DDP, DDP2
|
||||||
|
try:
|
||||||
|
torch.save(checkpoint, filepath)
|
||||||
|
except AttributeError:
|
||||||
|
if 'hparams' in checkpoint:
|
||||||
|
del checkpoint['hparams']
|
||||||
|
|
||||||
|
torch.save(checkpoint, filepath)
|
||||||
|
|
||||||
return filepath
|
return filepath
|
||||||
|
|
||||||
@@ -264,7 +315,7 @@ class TrainerIO(object):
|
|||||||
checkpoint = torch.load(filepath, map_location=lambda storage, loc: storage)
|
checkpoint = torch.load(filepath, map_location=lambda storage, loc: storage)
|
||||||
|
|
||||||
# load model state
|
# load model state
|
||||||
model = self.__get_model()
|
model = self.get_model()
|
||||||
|
|
||||||
# load the state_dict on the model automatically
|
# load the state_dict on the model automatically
|
||||||
model.load_state_dict(checkpoint['state_dict'])
|
model.load_state_dict(checkpoint['state_dict'])
|
||||||
@@ -278,7 +329,7 @@ class TrainerIO(object):
|
|||||||
# call model hook
|
# call model hook
|
||||||
model.on_hpc_load(checkpoint)
|
model.on_hpc_load(checkpoint)
|
||||||
|
|
||||||
print(f'restored hpc model from: {filepath}')
|
logging.info(f'restored hpc model from: {filepath}')
|
||||||
|
|
||||||
def max_ckpt_in_folder(self, path, name_key='ckpt_'):
|
def max_ckpt_in_folder(self, path, name_key='ckpt_'):
|
||||||
files = os.listdir(path)
|
files = os.listdir(path)
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import torch
|
||||||
|
import logging
|
||||||
|
from pytorch_lightning.callbacks import GradientAccumulationScheduler
|
||||||
|
|
||||||
|
|
||||||
|
class TrainerTrainingTricksMixin(object):
|
||||||
|
|
||||||
|
def clip_gradients(self):
|
||||||
|
if self.gradient_clip_val > 0:
|
||||||
|
model = self.get_model()
|
||||||
|
torch.nn.utils.clip_grad_norm_(model.parameters(), self.gradient_clip_val)
|
||||||
|
|
||||||
|
def print_nan_gradients(self):
|
||||||
|
model = self.get_model()
|
||||||
|
for param in model.parameters():
|
||||||
|
if torch.isnan(param.grad.float()).any():
|
||||||
|
logging.info(param, param.grad)
|
||||||
|
|
||||||
|
def configure_accumulated_gradients(self, accumulate_grad_batches):
|
||||||
|
self.accumulate_grad_batches = None
|
||||||
|
|
||||||
|
if isinstance(accumulate_grad_batches, dict):
|
||||||
|
self.accumulation_scheduler = GradientAccumulationScheduler(accumulate_grad_batches)
|
||||||
|
elif isinstance(accumulate_grad_batches, int):
|
||||||
|
schedule = {1: accumulate_grad_batches}
|
||||||
|
self.accumulation_scheduler = GradientAccumulationScheduler(schedule)
|
||||||
|
else:
|
||||||
|
raise TypeError("Gradient accumulation supports only int and dict types")
|
||||||
@@ -7,7 +7,6 @@ import os
|
|||||||
|
|
||||||
|
|
||||||
def add_default_args(parser, root_dir, rand_seed=None, possible_model_names=None):
|
def add_default_args(parser, root_dir, rand_seed=None, possible_model_names=None):
|
||||||
|
|
||||||
# training, test, val check intervals
|
# training, test, val check intervals
|
||||||
parser.add_argument('--eval_test_set', dest='eval_test_set', action='store_true',
|
parser.add_argument('--eval_test_set', dest='eval_test_set', action='store_true',
|
||||||
help='true = run test set also')
|
help='true = run test set also')
|
||||||
|
|||||||
+3
-1
@@ -4,4 +4,6 @@ twine==1.13.0
|
|||||||
numpy==1.16.4
|
numpy==1.16.4
|
||||||
torch>=1.2.0
|
torch>=1.2.0
|
||||||
torchvision>=0.3.0
|
torchvision>=0.3.0
|
||||||
pandas
|
pandas>=0.20.3
|
||||||
|
test-tube>=0.6.9
|
||||||
|
# future>=0.17.1 # required for buildins in setup.py
|
||||||
|
|||||||
@@ -11,12 +11,14 @@ markers =
|
|||||||
slow
|
slow
|
||||||
remote_data
|
remote_data
|
||||||
filterwarnings
|
filterwarnings
|
||||||
|
gpus_param_tests
|
||||||
|
|
||||||
[pycodestyle]
|
[pycodestyle]
|
||||||
ignore = E731,W504
|
ignore = E731,W504
|
||||||
max-line-length = 120
|
max-line-length = 120
|
||||||
|
|
||||||
[coverage:report]
|
[coverage:report]
|
||||||
|
# TODO: this looks suspicion, it should be reviewed
|
||||||
exclude_lines =
|
exclude_lines =
|
||||||
pragma: no cover
|
pragma: no cover
|
||||||
def __repr__
|
def __repr__
|
||||||
@@ -39,7 +41,6 @@ exclude_lines =
|
|||||||
break
|
break
|
||||||
pass
|
pass
|
||||||
os.makedirs
|
os.makedirs
|
||||||
|
|
||||||
omit =
|
omit =
|
||||||
pytorch_lightning/callbacks/pt_callbacks.py
|
pytorch_lightning/callbacks/pt_callbacks.py
|
||||||
tests/test_models.py
|
tests/test_models.py
|
||||||
@@ -48,5 +49,12 @@ omit =
|
|||||||
examples/templates
|
examples/templates
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
ignore = E731,W504,F401,F841
|
# TODO: this should be 88 or 100 according PEP8
|
||||||
max-line-length = 120
|
max-line-length = 120
|
||||||
|
exclude = .tox,*.egg,build,temp,examples/*
|
||||||
|
select = E,W,F
|
||||||
|
doctests = True
|
||||||
|
verbose = 2
|
||||||
|
# https://pep8.readthedocs.io/en/latest/intro.html#error-codes
|
||||||
|
format = pylint
|
||||||
|
ignore = E731,W504,F401,F841
|
||||||
|
|||||||
@@ -1,12 +1,37 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import os
|
||||||
|
from io import open
|
||||||
# Always prefer setuptools over distutils
|
# Always prefer setuptools over distutils
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
# https://packaging.python.org/guides/single-sourcing-package-version/
|
try:
|
||||||
|
import builtins
|
||||||
|
except ImportError:
|
||||||
|
import __builtin__ as builtins
|
||||||
|
|
||||||
|
# https://packaging.python.org/guides/single-sourcing-package-version/
|
||||||
# http://blog.ionelmc.ro/2014/05/25/python-packaging/
|
# http://blog.ionelmc.ro/2014/05/25/python-packaging/
|
||||||
|
|
||||||
|
PATH_ROOT = os.path.dirname(__file__)
|
||||||
|
builtins.__LIGHTNING_SETUP__ = True
|
||||||
|
|
||||||
|
import pytorch_lightning # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
def load_requirements(path_dir=PATH_ROOT, comment_char='#'):
|
||||||
|
with open(os.path.join(path_dir, 'requirements.txt'), 'r') as file:
|
||||||
|
lines = [ln.strip() for ln in file.readlines()]
|
||||||
|
reqs = []
|
||||||
|
for ln in lines:
|
||||||
|
# filer all comments
|
||||||
|
if comment_char in ln:
|
||||||
|
ln = ln[:ln.index(comment_char)]
|
||||||
|
if ln: # if requirement is not empty
|
||||||
|
reqs.append(ln)
|
||||||
|
return reqs
|
||||||
|
|
||||||
|
|
||||||
# https://packaging.python.org/discussions/install-requires-vs-requirements /
|
# https://packaging.python.org/discussions/install-requires-vs-requirements /
|
||||||
# keep the meta-data here for simplicity in reading this file... it's not obvious
|
# keep the meta-data here for simplicity in reading this file... it's not obvious
|
||||||
# what happens and to non-engineers they won't know to look in init ...
|
# what happens and to non-engineers they won't know to look in init ...
|
||||||
@@ -14,26 +39,22 @@ from setuptools import setup, find_packages
|
|||||||
# engineer specific practices
|
# engineer specific practices
|
||||||
setup(
|
setup(
|
||||||
name='pytorch-lightning',
|
name='pytorch-lightning',
|
||||||
version='0.5.1.2',
|
version=pytorch_lightning.__version__,
|
||||||
description='The Keras for ML researchers using PyTorch',
|
description=pytorch_lightning.__docs__,
|
||||||
author='William Falcon',
|
author=pytorch_lightning.__author__,
|
||||||
author_email='waf2107@columbia.edu',
|
author_email=pytorch_lightning.__author_email__,
|
||||||
url='https://github.com/williamFalcon/pytorch-lightning',
|
url=pytorch_lightning.__homepage__,
|
||||||
download_url='https://github.com/williamFalcon/pytorch-lightning',
|
download_url='https://github.com/williamFalcon/pytorch-lightning',
|
||||||
license='Apache-2',
|
license=pytorch_lightning.__license__,
|
||||||
packages=find_packages(),
|
packages=find_packages(exclude=['examples']),
|
||||||
long_description=open('README.md', encoding='utf-8').read(),
|
# long_description=open('README.md', encoding='utf-8').read(),
|
||||||
long_description_content_type='text/markdown',
|
# long_description_content_type='text/markdown',
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
keywords=['deep learning', 'pytorch', 'AI'],
|
keywords=['deep learning', 'pytorch', 'AI'],
|
||||||
python_requires='>=3.6',
|
python_requires='>=3.6',
|
||||||
install_requires=[
|
setup_requires=[],
|
||||||
'torch>=1.2.0',
|
install_requires=load_requirements(PATH_ROOT),
|
||||||
'tqdm>=4.35.0',
|
|
||||||
'test-tube>=0.6.9',
|
|
||||||
'pandas>=0.20.3',
|
|
||||||
],
|
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'Environment :: Console',
|
'Environment :: Console',
|
||||||
'Natural Language :: English',
|
'Natural Language :: English',
|
||||||
|
|||||||
+3
-16
@@ -1,4 +1,7 @@
|
|||||||
# PyTorch-Lightning Tests
|
# PyTorch-Lightning Tests
|
||||||
|
Most PL tests train a full MNIST model under various trainer conditions (ddp, ddp2+amp, etc...).
|
||||||
|
This provides testing for most combinations of important settings.
|
||||||
|
The tests expect the model to perform to a reasonable degree of testing accuracy to pass.
|
||||||
|
|
||||||
## Running tests
|
## Running tests
|
||||||
The automatic travis tests ONLY run CPU-based tests. Although these cover most of the use cases,
|
The automatic travis tests ONLY run CPU-based tests. Although these cover most of the use cases,
|
||||||
@@ -26,22 +29,6 @@ The GPU machine must have:
|
|||||||
2. [NVIDIA-apex](https://github.com/NVIDIA/apex#linux) installed.
|
2. [NVIDIA-apex](https://github.com/NVIDIA/apex#linux) installed.
|
||||||
|
|
||||||
|
|
||||||
### test_models.py
|
|
||||||
This file fits a tiny model on MNIST using these different set-ups.
|
|
||||||
1. CPU only.
|
|
||||||
2. Single GPU with DP.
|
|
||||||
3. Multiple (2) GPUs using DP.
|
|
||||||
3. Multiple (2) GPUs using DDP.
|
|
||||||
3. Multiple (2) GPUs using DP + apex (for 16-bit precision).
|
|
||||||
3. Multiple (2) GPUs using DDP + apex (for 16-bit precision).
|
|
||||||
|
|
||||||
For each set up it also tests:
|
|
||||||
1. model saving.
|
|
||||||
2. model loading.
|
|
||||||
3. predicting with a loaded model.
|
|
||||||
4. simulated save from HPC signal.
|
|
||||||
5. simulated load from HPC signal.
|
|
||||||
|
|
||||||
## Running Coverage
|
## Running Coverage
|
||||||
Make sure to run coverage on a GPU machine with at least 2 GPUs and NVIDIA apex installed.
|
Make sure to run coverage on a GPU machine with at least 2 GPUs and NVIDIA apex installed.
|
||||||
|
|
||||||
|
|||||||
+57
-66
@@ -1,20 +1,14 @@
|
|||||||
from pytorch_lightning import Trainer
|
|
||||||
from examples import LightningTemplateModel
|
|
||||||
from pytorch_lightning.testing import LightningTestModel
|
|
||||||
from argparse import Namespace
|
|
||||||
from test_tube import Experiment
|
|
||||||
from pytorch_lightning.callbacks import ModelCheckpoint
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
import pytorch_lightning as pl
|
|
||||||
import torch
|
import torch
|
||||||
from torch.nn import functional as F
|
from torch.nn import functional as F
|
||||||
from torch.utils.data import DataLoader
|
from torch.utils.data import DataLoader
|
||||||
from torchvision.datasets import MNIST
|
from torchvision.datasets import MNIST
|
||||||
import numpy as np
|
|
||||||
import pdb
|
import pytorch_lightning as pl
|
||||||
from . import test_models
|
|
||||||
|
|
||||||
|
# from test_models import assert_ok_test_acc, load_model, \
|
||||||
|
# clear_save_dir, get_test_tube_logger, get_hparams, init_save_dir, \
|
||||||
|
# init_checkpoint_callback, reset_seed, set_random_master_port
|
||||||
|
|
||||||
|
|
||||||
class CoolModel(pl.LightningModule):
|
class CoolModel(pl.LightningModule):
|
||||||
@@ -59,56 +53,53 @@ class CoolModel(pl.LightningModule):
|
|||||||
def test_dataloader(self):
|
def test_dataloader(self):
|
||||||
return DataLoader(MNIST('path/to/save', train=False), batch_size=32)
|
return DataLoader(MNIST('path/to/save', train=False), batch_size=32)
|
||||||
|
|
||||||
|
#
|
||||||
def main():
|
# def main():
|
||||||
"""
|
# reset_seed()
|
||||||
Make sure DDP + AMP continue training correctly
|
# set_random_master_port()
|
||||||
:return:
|
#
|
||||||
"""
|
# hparams = get_hparams()
|
||||||
"""
|
# model = LightningTestModel(hparams)
|
||||||
Make sure DDP2 works
|
#
|
||||||
:return:
|
# save_dir = init_save_dir()
|
||||||
"""
|
#
|
||||||
hparams = test_models.get_hparams()
|
# # exp file to get meta
|
||||||
model = LightningTestModel(hparams)
|
# logger = get_test_tube_logger(False)
|
||||||
|
#
|
||||||
save_dir = test_models.init_save_dir()
|
# print(logger.debug)
|
||||||
|
#
|
||||||
# logger file to get meta
|
# # exp file to get weights
|
||||||
logger = test_models.get_test_tube_logger(False)
|
# checkpoint = init_checkpoint_callback(logger)
|
||||||
logger.log_hyperparams(hparams)
|
#
|
||||||
logger.save()
|
# trainer_options = dict(
|
||||||
|
# show_progress_bar=False,
|
||||||
# logger file to get weights
|
# max_nb_epochs=1,
|
||||||
checkpoint = ModelCheckpoint(save_dir)
|
# train_percent_check=0.4,
|
||||||
|
# val_percent_check=0.2,
|
||||||
trainer_options = dict(
|
# checkpoint_callback=checkpoint,
|
||||||
show_progress_bar=True,
|
# logger=logger,
|
||||||
max_nb_epochs=1,
|
# gpus=[0, 1],
|
||||||
train_percent_check=0.4,
|
# distributed_backend='ddp'
|
||||||
val_percent_check=0.2,
|
# )
|
||||||
checkpoint_callback=checkpoint,
|
#
|
||||||
logger=logger,
|
# # fit model
|
||||||
gpus=[0, 1],
|
# trainer = Trainer(**trainer_options)
|
||||||
distributed_backend='dp'
|
# result = trainer.fit(model)
|
||||||
)
|
#
|
||||||
|
# exp = logger.experiment
|
||||||
# fit model
|
# print(os.listdir(exp.get_data_path(exp.name, exp.version)))
|
||||||
trainer = Trainer(**trainer_options)
|
#
|
||||||
result = trainer.fit(model)
|
# # correct result and ok accuracy
|
||||||
|
# assert result == 1, 'training failed to complete'
|
||||||
# correct result and ok accuracy
|
# pretrained_model = load_model(logger.experiment, save_dir,
|
||||||
assert result == 1, 'training failed to complete'
|
# module_class=LightningTestModel)
|
||||||
pretrained_model = test_models.load_model(logger.experiment, save_dir,
|
#
|
||||||
module_class=LightningTestModel)
|
# # run test set
|
||||||
|
# new_trainer = Trainer(**trainer_options)
|
||||||
new_trainer = Trainer(**trainer_options)
|
# new_trainer.test(pretrained_model)
|
||||||
new_trainer.test(pretrained_model)
|
#
|
||||||
|
# # test we have good test accuracy
|
||||||
# test we have good test accuracy
|
# clear_save_dir()
|
||||||
test_models.assert_ok_test_acc(new_trainer)
|
#
|
||||||
test_models.clear_save_dir()
|
# if __name__ == '__main__':
|
||||||
|
# main()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|||||||
@@ -7,3 +7,4 @@ flake8
|
|||||||
check-manifest
|
check-manifest
|
||||||
test_tube
|
test_tube
|
||||||
mlflow
|
mlflow
|
||||||
|
comet_ml
|
||||||
|
|||||||
@@ -0,0 +1,409 @@
|
|||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from pytorch_lightning import Trainer
|
||||||
|
from pytorch_lightning.callbacks import ModelCheckpoint
|
||||||
|
from pytorch_lightning.testing import LightningTestModel
|
||||||
|
from . import testing_utils
|
||||||
|
|
||||||
|
|
||||||
|
def test_running_test_pretrained_model_ddp():
|
||||||
|
"""Verify test() on pretrained model"""
|
||||||
|
if not testing_utils.can_run_gpu_test():
|
||||||
|
return
|
||||||
|
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
testing_utils.set_random_master_port()
|
||||||
|
|
||||||
|
hparams = testing_utils.get_hparams()
|
||||||
|
model = LightningTestModel(hparams)
|
||||||
|
|
||||||
|
save_dir = testing_utils.init_save_dir()
|
||||||
|
|
||||||
|
# exp file to get meta
|
||||||
|
logger = testing_utils.get_test_tube_logger(False)
|
||||||
|
|
||||||
|
# exp file to get weights
|
||||||
|
checkpoint = testing_utils.init_checkpoint_callback(logger)
|
||||||
|
|
||||||
|
trainer_options = dict(
|
||||||
|
show_progress_bar=False,
|
||||||
|
max_nb_epochs=1,
|
||||||
|
train_percent_check=0.4,
|
||||||
|
val_percent_check=0.2,
|
||||||
|
checkpoint_callback=checkpoint,
|
||||||
|
logger=logger,
|
||||||
|
gpus=[0, 1],
|
||||||
|
distributed_backend='ddp'
|
||||||
|
)
|
||||||
|
|
||||||
|
# fit model
|
||||||
|
trainer = Trainer(**trainer_options)
|
||||||
|
result = trainer.fit(model)
|
||||||
|
|
||||||
|
exp = logger.experiment
|
||||||
|
logging.info(os.listdir(exp.get_data_path(exp.name, exp.version)))
|
||||||
|
|
||||||
|
# correct result and ok accuracy
|
||||||
|
assert result == 1, 'training failed to complete'
|
||||||
|
pretrained_model = testing_utils.load_model(logger.experiment,
|
||||||
|
trainer.checkpoint_callback.filepath,
|
||||||
|
module_class=LightningTestModel)
|
||||||
|
|
||||||
|
# run test set
|
||||||
|
new_trainer = Trainer(**trainer_options)
|
||||||
|
new_trainer.test(pretrained_model)
|
||||||
|
|
||||||
|
for dataloader in model.test_dataloader():
|
||||||
|
testing_utils.run_prediction(dataloader, pretrained_model)
|
||||||
|
|
||||||
|
testing_utils.clear_save_dir()
|
||||||
|
|
||||||
|
|
||||||
|
def test_running_test_pretrained_model():
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
"""Verify test() on pretrained model"""
|
||||||
|
hparams = testing_utils.get_hparams()
|
||||||
|
model = LightningTestModel(hparams)
|
||||||
|
|
||||||
|
save_dir = testing_utils.init_save_dir()
|
||||||
|
|
||||||
|
# logger file to get meta
|
||||||
|
logger = testing_utils.get_test_tube_logger(False)
|
||||||
|
|
||||||
|
# logger file to get weights
|
||||||
|
checkpoint = testing_utils.init_checkpoint_callback(logger)
|
||||||
|
|
||||||
|
trainer_options = dict(
|
||||||
|
show_progress_bar=False,
|
||||||
|
max_nb_epochs=1,
|
||||||
|
train_percent_check=0.4,
|
||||||
|
val_percent_check=0.2,
|
||||||
|
checkpoint_callback=checkpoint,
|
||||||
|
logger=logger
|
||||||
|
)
|
||||||
|
|
||||||
|
# fit model
|
||||||
|
trainer = Trainer(**trainer_options)
|
||||||
|
result = trainer.fit(model)
|
||||||
|
|
||||||
|
# correct result and ok accuracy
|
||||||
|
assert result == 1, 'training failed to complete'
|
||||||
|
pretrained_model = testing_utils.load_model(
|
||||||
|
logger.experiment, trainer.checkpoint_callback.filepath, module_class=LightningTestModel
|
||||||
|
)
|
||||||
|
|
||||||
|
new_trainer = Trainer(**trainer_options)
|
||||||
|
new_trainer.test(pretrained_model)
|
||||||
|
|
||||||
|
# test we have good test accuracy
|
||||||
|
testing_utils.assert_ok_test_acc(new_trainer)
|
||||||
|
testing_utils.clear_save_dir()
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_model_from_checkpoint():
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
"""Verify test() on pretrained model"""
|
||||||
|
hparams = testing_utils.get_hparams()
|
||||||
|
model = LightningTestModel(hparams)
|
||||||
|
|
||||||
|
save_dir = testing_utils.init_save_dir()
|
||||||
|
|
||||||
|
trainer_options = dict(
|
||||||
|
show_progress_bar=False,
|
||||||
|
max_nb_epochs=1,
|
||||||
|
train_percent_check=0.4,
|
||||||
|
val_percent_check=0.2,
|
||||||
|
checkpoint_callback=True,
|
||||||
|
logger=False,
|
||||||
|
default_save_path=save_dir
|
||||||
|
)
|
||||||
|
|
||||||
|
# fit model
|
||||||
|
trainer = Trainer(**trainer_options)
|
||||||
|
result = trainer.fit(model)
|
||||||
|
|
||||||
|
# correct result and ok accuracy
|
||||||
|
assert result == 1, 'training failed to complete'
|
||||||
|
pretrained_model = LightningTestModel.load_from_checkpoint(
|
||||||
|
os.path.join(trainer.checkpoint_callback.filepath, "_ckpt_epoch_1.ckpt")
|
||||||
|
)
|
||||||
|
|
||||||
|
# test that hparams loaded correctly
|
||||||
|
for k, v in vars(hparams).items():
|
||||||
|
assert getattr(pretrained_model.hparams, k) == v
|
||||||
|
|
||||||
|
new_trainer = Trainer(**trainer_options)
|
||||||
|
new_trainer.test(pretrained_model)
|
||||||
|
|
||||||
|
# test we have good test accuracy
|
||||||
|
testing_utils.assert_ok_test_acc(new_trainer)
|
||||||
|
testing_utils.clear_save_dir()
|
||||||
|
|
||||||
|
|
||||||
|
def test_running_test_pretrained_model_dp():
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
"""Verify test() on pretrained model"""
|
||||||
|
if not testing_utils.can_run_gpu_test():
|
||||||
|
return
|
||||||
|
|
||||||
|
hparams = testing_utils.get_hparams()
|
||||||
|
model = LightningTestModel(hparams)
|
||||||
|
|
||||||
|
save_dir = testing_utils.init_save_dir()
|
||||||
|
|
||||||
|
# logger file to get meta
|
||||||
|
logger = testing_utils.get_test_tube_logger(False)
|
||||||
|
|
||||||
|
# logger file to get weights
|
||||||
|
checkpoint = testing_utils.init_checkpoint_callback(logger)
|
||||||
|
|
||||||
|
trainer_options = dict(
|
||||||
|
show_progress_bar=True,
|
||||||
|
max_nb_epochs=1,
|
||||||
|
train_percent_check=0.4,
|
||||||
|
val_percent_check=0.2,
|
||||||
|
checkpoint_callback=checkpoint,
|
||||||
|
logger=logger,
|
||||||
|
gpus=[0, 1],
|
||||||
|
distributed_backend='dp'
|
||||||
|
)
|
||||||
|
|
||||||
|
# fit model
|
||||||
|
trainer = Trainer(**trainer_options)
|
||||||
|
result = trainer.fit(model)
|
||||||
|
|
||||||
|
# correct result and ok accuracy
|
||||||
|
assert result == 1, 'training failed to complete'
|
||||||
|
pretrained_model = testing_utils.load_model(logger.experiment,
|
||||||
|
trainer.checkpoint_callback.filepath,
|
||||||
|
module_class=LightningTestModel)
|
||||||
|
|
||||||
|
new_trainer = Trainer(**trainer_options)
|
||||||
|
new_trainer.test(pretrained_model)
|
||||||
|
|
||||||
|
# test we have good test accuracy
|
||||||
|
testing_utils.assert_ok_test_acc(new_trainer)
|
||||||
|
testing_utils.clear_save_dir()
|
||||||
|
|
||||||
|
|
||||||
|
def test_dp_resume():
|
||||||
|
"""
|
||||||
|
Make sure DP continues training correctly
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if not testing_utils.can_run_gpu_test():
|
||||||
|
return
|
||||||
|
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
hparams = testing_utils.get_hparams()
|
||||||
|
model = LightningTestModel(hparams)
|
||||||
|
|
||||||
|
trainer_options = dict(
|
||||||
|
show_progress_bar=True,
|
||||||
|
max_nb_epochs=2,
|
||||||
|
gpus=2,
|
||||||
|
distributed_backend='dp',
|
||||||
|
)
|
||||||
|
|
||||||
|
save_dir = testing_utils.init_save_dir()
|
||||||
|
|
||||||
|
# get logger
|
||||||
|
logger = testing_utils.get_test_tube_logger(debug=False)
|
||||||
|
|
||||||
|
# exp file to get weights
|
||||||
|
# logger file to get weights
|
||||||
|
checkpoint = testing_utils.init_checkpoint_callback(logger)
|
||||||
|
|
||||||
|
# add these to the trainer options
|
||||||
|
trainer_options['logger'] = logger
|
||||||
|
trainer_options['checkpoint_callback'] = checkpoint
|
||||||
|
|
||||||
|
# fit model
|
||||||
|
trainer = Trainer(**trainer_options)
|
||||||
|
trainer.is_slurm_managing_tasks = True
|
||||||
|
result = trainer.fit(model)
|
||||||
|
|
||||||
|
# track epoch before saving
|
||||||
|
real_global_epoch = trainer.current_epoch
|
||||||
|
|
||||||
|
# correct result and ok accuracy
|
||||||
|
assert result == 1, 'amp + dp model failed to complete'
|
||||||
|
|
||||||
|
# ---------------------------
|
||||||
|
# HPC LOAD/SAVE
|
||||||
|
# ---------------------------
|
||||||
|
# save
|
||||||
|
trainer.hpc_save(save_dir, logger)
|
||||||
|
|
||||||
|
# init new trainer
|
||||||
|
new_logger = testing_utils.get_test_tube_logger(version=logger.version)
|
||||||
|
trainer_options['logger'] = new_logger
|
||||||
|
trainer_options['checkpoint_callback'] = ModelCheckpoint(save_dir)
|
||||||
|
trainer_options['train_percent_check'] = 0.2
|
||||||
|
trainer_options['val_percent_check'] = 0.2
|
||||||
|
trainer_options['max_nb_epochs'] = 1
|
||||||
|
new_trainer = Trainer(**trainer_options)
|
||||||
|
|
||||||
|
# set the epoch start hook so we can predict before the model does the full training
|
||||||
|
def assert_good_acc():
|
||||||
|
assert new_trainer.current_epoch == real_global_epoch and new_trainer.current_epoch > 0
|
||||||
|
|
||||||
|
# if model and state loaded correctly, predictions will be good even though we
|
||||||
|
# haven't trained with the new loaded model
|
||||||
|
dp_model = new_trainer.model
|
||||||
|
dp_model.eval()
|
||||||
|
|
||||||
|
dataloader = trainer.get_train_dataloader()
|
||||||
|
testing_utils.run_prediction(dataloader, dp_model, dp=True)
|
||||||
|
|
||||||
|
# new model
|
||||||
|
model = LightningTestModel(hparams)
|
||||||
|
model.on_sanity_check_start = assert_good_acc
|
||||||
|
|
||||||
|
# fit new model which should load hpc weights
|
||||||
|
new_trainer.fit(model)
|
||||||
|
|
||||||
|
# test freeze on gpu
|
||||||
|
model.freeze()
|
||||||
|
model.unfreeze()
|
||||||
|
|
||||||
|
testing_utils.clear_save_dir()
|
||||||
|
|
||||||
|
|
||||||
|
def test_cpu_restore_training():
|
||||||
|
"""
|
||||||
|
Verify continue training session on CPU
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
hparams = testing_utils.get_hparams()
|
||||||
|
model = LightningTestModel(hparams)
|
||||||
|
|
||||||
|
save_dir = testing_utils.init_save_dir()
|
||||||
|
|
||||||
|
# logger file to get meta
|
||||||
|
test_logger_version = 10
|
||||||
|
logger = testing_utils.get_test_tube_logger(False, version=test_logger_version)
|
||||||
|
|
||||||
|
trainer_options = dict(
|
||||||
|
max_nb_epochs=2,
|
||||||
|
val_check_interval=0.50,
|
||||||
|
val_percent_check=0.2,
|
||||||
|
train_percent_check=0.2,
|
||||||
|
logger=logger,
|
||||||
|
checkpoint_callback=ModelCheckpoint(save_dir)
|
||||||
|
)
|
||||||
|
|
||||||
|
# fit model
|
||||||
|
trainer = Trainer(**trainer_options)
|
||||||
|
result = trainer.fit(model)
|
||||||
|
real_global_epoch = trainer.current_epoch
|
||||||
|
|
||||||
|
# traning complete
|
||||||
|
assert result == 1, 'amp + ddp model failed to complete'
|
||||||
|
|
||||||
|
# wipe-out trainer and model
|
||||||
|
# retrain with not much data... this simulates picking training back up after slurm
|
||||||
|
# we want to see if the weights come back correctly
|
||||||
|
new_logger = testing_utils.get_test_tube_logger(False, version=test_logger_version)
|
||||||
|
trainer_options = dict(
|
||||||
|
max_nb_epochs=2,
|
||||||
|
val_check_interval=0.50,
|
||||||
|
val_percent_check=0.2,
|
||||||
|
train_percent_check=0.2,
|
||||||
|
logger=new_logger,
|
||||||
|
checkpoint_callback=ModelCheckpoint(save_dir),
|
||||||
|
)
|
||||||
|
trainer = Trainer(**trainer_options)
|
||||||
|
model = LightningTestModel(hparams)
|
||||||
|
|
||||||
|
# set the epoch start hook so we can predict before the model does the full training
|
||||||
|
def assert_good_acc():
|
||||||
|
assert trainer.current_epoch == real_global_epoch
|
||||||
|
assert trainer.current_epoch >= 0
|
||||||
|
|
||||||
|
# if model and state loaded correctly, predictions will be good even though we
|
||||||
|
# haven't trained with the new loaded model
|
||||||
|
trainer.model.eval()
|
||||||
|
for dataloader in trainer.get_val_dataloaders():
|
||||||
|
testing_utils.run_prediction(dataloader, trainer.model)
|
||||||
|
|
||||||
|
model.on_sanity_check_start = assert_good_acc
|
||||||
|
|
||||||
|
# by calling fit again, we trigger training, loading weights from the cluster
|
||||||
|
# and our hook to predict using current model before any more weight updates
|
||||||
|
trainer.fit(model)
|
||||||
|
|
||||||
|
testing_utils.clear_save_dir()
|
||||||
|
|
||||||
|
|
||||||
|
def test_model_saving_loading():
|
||||||
|
"""
|
||||||
|
Tests use case where trainer saves the model, and user loads it from tags independently
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
hparams = testing_utils.get_hparams()
|
||||||
|
model = LightningTestModel(hparams)
|
||||||
|
|
||||||
|
save_dir = testing_utils.init_save_dir()
|
||||||
|
|
||||||
|
# logger file to get meta
|
||||||
|
logger = testing_utils.get_test_tube_logger(False)
|
||||||
|
|
||||||
|
trainer_options = dict(
|
||||||
|
max_nb_epochs=1,
|
||||||
|
logger=logger,
|
||||||
|
checkpoint_callback=ModelCheckpoint(save_dir)
|
||||||
|
)
|
||||||
|
|
||||||
|
# fit model
|
||||||
|
trainer = Trainer(**trainer_options)
|
||||||
|
result = trainer.fit(model)
|
||||||
|
|
||||||
|
# traning complete
|
||||||
|
assert result == 1, 'amp + ddp model failed to complete'
|
||||||
|
|
||||||
|
# make a prediction
|
||||||
|
for dataloader in model.test_dataloader():
|
||||||
|
for batch in dataloader:
|
||||||
|
break
|
||||||
|
|
||||||
|
x, y = batch
|
||||||
|
x = x.view(x.size(0), -1)
|
||||||
|
|
||||||
|
# generate preds before saving model
|
||||||
|
model.eval()
|
||||||
|
pred_before_saving = model(x)
|
||||||
|
|
||||||
|
# save model
|
||||||
|
new_weights_path = os.path.join(save_dir, 'save_test.ckpt')
|
||||||
|
trainer.save_checkpoint(new_weights_path)
|
||||||
|
|
||||||
|
# load new model
|
||||||
|
tags_path = logger.experiment.get_data_path(logger.experiment.name, logger.experiment.version)
|
||||||
|
tags_path = os.path.join(tags_path, 'meta_tags.csv')
|
||||||
|
model_2 = LightningTestModel.load_from_metrics(weights_path=new_weights_path,
|
||||||
|
tags_csv=tags_path)
|
||||||
|
model_2.eval()
|
||||||
|
|
||||||
|
# make prediction
|
||||||
|
# assert that both predictions are the same
|
||||||
|
new_pred = model_2(x)
|
||||||
|
assert torch.all(torch.eq(pred_before_saving, new_pred)).item() == 1
|
||||||
|
|
||||||
|
testing_utils.clear_save_dir()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
pytest.main([__file__])
|
||||||
@@ -0,0 +1,399 @@
|
|||||||
|
import warnings
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from pytorch_lightning import Trainer, data_loader
|
||||||
|
from pytorch_lightning.callbacks import (
|
||||||
|
EarlyStopping,
|
||||||
|
)
|
||||||
|
from pytorch_lightning.testing import (
|
||||||
|
LightningTestModel,
|
||||||
|
LightningTestModelBase,
|
||||||
|
LightningTestMixin,
|
||||||
|
)
|
||||||
|
from . import testing_utils
|
||||||
|
|
||||||
|
|
||||||
|
def test_early_stopping_cpu_model():
|
||||||
|
"""
|
||||||
|
Test each of the trainer options
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
stopping = EarlyStopping(monitor='val_loss')
|
||||||
|
trainer_options = dict(
|
||||||
|
early_stop_callback=stopping,
|
||||||
|
gradient_clip_val=1.0,
|
||||||
|
overfit_pct=0.20,
|
||||||
|
track_grad_norm=2,
|
||||||
|
print_nan_grads=True,
|
||||||
|
show_progress_bar=True,
|
||||||
|
logger=testing_utils.get_test_tube_logger(),
|
||||||
|
train_percent_check=0.1,
|
||||||
|
val_percent_check=0.1
|
||||||
|
)
|
||||||
|
|
||||||
|
model, hparams = testing_utils.get_model()
|
||||||
|
testing_utils.run_gpu_model_test(trainer_options, model, hparams, on_gpu=False)
|
||||||
|
|
||||||
|
# test freeze on cpu
|
||||||
|
model.freeze()
|
||||||
|
model.unfreeze()
|
||||||
|
|
||||||
|
|
||||||
|
def test_lbfgs_cpu_model():
|
||||||
|
"""
|
||||||
|
Test each of the trainer options
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
trainer_options = dict(
|
||||||
|
max_nb_epochs=1,
|
||||||
|
print_nan_grads=True,
|
||||||
|
show_progress_bar=False,
|
||||||
|
weights_summary='top',
|
||||||
|
train_percent_check=1.0,
|
||||||
|
val_percent_check=0.2
|
||||||
|
)
|
||||||
|
|
||||||
|
model, hparams = testing_utils.get_model(use_test_model=True, lbfgs=True)
|
||||||
|
testing_utils.run_model_test_no_loggers(trainer_options,
|
||||||
|
model, hparams, on_gpu=False, min_acc=0.30)
|
||||||
|
|
||||||
|
testing_utils.clear_save_dir()
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_logger_callbacks_cpu_model():
|
||||||
|
"""
|
||||||
|
Test each of the trainer options
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
trainer_options = dict(
|
||||||
|
max_nb_epochs=1,
|
||||||
|
gradient_clip_val=1.0,
|
||||||
|
overfit_pct=0.20,
|
||||||
|
print_nan_grads=True,
|
||||||
|
show_progress_bar=False,
|
||||||
|
train_percent_check=0.01,
|
||||||
|
val_percent_check=0.01
|
||||||
|
)
|
||||||
|
|
||||||
|
model, hparams = testing_utils.get_model()
|
||||||
|
testing_utils.run_model_test_no_loggers(trainer_options, model, hparams, on_gpu=False)
|
||||||
|
|
||||||
|
# test freeze on cpu
|
||||||
|
model.freeze()
|
||||||
|
model.unfreeze()
|
||||||
|
|
||||||
|
testing_utils.clear_save_dir()
|
||||||
|
|
||||||
|
|
||||||
|
def test_running_test_after_fitting():
|
||||||
|
"""Verify test() on fitted model"""
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
hparams = testing_utils.get_hparams()
|
||||||
|
model = LightningTestModel(hparams)
|
||||||
|
|
||||||
|
save_dir = testing_utils.init_save_dir()
|
||||||
|
|
||||||
|
# logger file to get meta
|
||||||
|
logger = testing_utils.get_test_tube_logger(False)
|
||||||
|
|
||||||
|
# logger file to get weights
|
||||||
|
checkpoint = testing_utils.init_checkpoint_callback(logger)
|
||||||
|
|
||||||
|
trainer_options = dict(
|
||||||
|
show_progress_bar=False,
|
||||||
|
max_nb_epochs=1,
|
||||||
|
train_percent_check=0.4,
|
||||||
|
val_percent_check=0.2,
|
||||||
|
test_percent_check=0.2,
|
||||||
|
checkpoint_callback=checkpoint,
|
||||||
|
logger=logger
|
||||||
|
)
|
||||||
|
|
||||||
|
# fit model
|
||||||
|
trainer = Trainer(**trainer_options)
|
||||||
|
result = trainer.fit(model)
|
||||||
|
|
||||||
|
assert result == 1, 'training failed to complete'
|
||||||
|
|
||||||
|
trainer.test()
|
||||||
|
|
||||||
|
# test we have good test accuracy
|
||||||
|
testing_utils.assert_ok_test_acc(trainer)
|
||||||
|
|
||||||
|
testing_utils.clear_save_dir()
|
||||||
|
|
||||||
|
|
||||||
|
def test_running_test_without_val():
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
"""Verify test() works on a model with no val_loader"""
|
||||||
|
|
||||||
|
class CurrentTestModel(LightningTestMixin, LightningTestModelBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
hparams = testing_utils.get_hparams()
|
||||||
|
model = CurrentTestModel(hparams)
|
||||||
|
|
||||||
|
save_dir = testing_utils.init_save_dir()
|
||||||
|
|
||||||
|
# logger file to get meta
|
||||||
|
logger = testing_utils.get_test_tube_logger(False)
|
||||||
|
|
||||||
|
# logger file to get weights
|
||||||
|
checkpoint = testing_utils.init_checkpoint_callback(logger)
|
||||||
|
|
||||||
|
trainer_options = dict(
|
||||||
|
show_progress_bar=False,
|
||||||
|
max_nb_epochs=1,
|
||||||
|
train_percent_check=0.4,
|
||||||
|
val_percent_check=0.2,
|
||||||
|
test_percent_check=0.2,
|
||||||
|
checkpoint_callback=checkpoint,
|
||||||
|
logger=logger
|
||||||
|
)
|
||||||
|
|
||||||
|
# fit model
|
||||||
|
trainer = Trainer(**trainer_options)
|
||||||
|
result = trainer.fit(model)
|
||||||
|
|
||||||
|
assert result == 1, 'training failed to complete'
|
||||||
|
|
||||||
|
trainer.test()
|
||||||
|
|
||||||
|
# test we have good test accuracy
|
||||||
|
testing_utils.assert_ok_test_acc(trainer)
|
||||||
|
|
||||||
|
testing_utils.clear_save_dir()
|
||||||
|
|
||||||
|
|
||||||
|
def test_single_gpu_batch_parse():
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
if not testing_utils.can_run_gpu_test():
|
||||||
|
return
|
||||||
|
|
||||||
|
trainer = Trainer()
|
||||||
|
|
||||||
|
# batch is just a tensor
|
||||||
|
batch = torch.rand(2, 3)
|
||||||
|
batch = trainer.transfer_batch_to_gpu(batch, 0)
|
||||||
|
assert batch.device.index == 0 and batch.type() == 'torch.cuda.FloatTensor'
|
||||||
|
|
||||||
|
# tensor list
|
||||||
|
batch = [torch.rand(2, 3), torch.rand(2, 3)]
|
||||||
|
batch = trainer.transfer_batch_to_gpu(batch, 0)
|
||||||
|
assert batch[0].device.index == 0 and batch[0].type() == 'torch.cuda.FloatTensor'
|
||||||
|
assert batch[1].device.index == 0 and batch[1].type() == 'torch.cuda.FloatTensor'
|
||||||
|
|
||||||
|
# tensor list of lists
|
||||||
|
batch = [[torch.rand(2, 3), torch.rand(2, 3)]]
|
||||||
|
batch = trainer.transfer_batch_to_gpu(batch, 0)
|
||||||
|
assert batch[0][0].device.index == 0 and batch[0][0].type() == 'torch.cuda.FloatTensor'
|
||||||
|
assert batch[0][1].device.index == 0 and batch[0][1].type() == 'torch.cuda.FloatTensor'
|
||||||
|
|
||||||
|
# tensor dict
|
||||||
|
batch = [{'a': torch.rand(2, 3), 'b': torch.rand(2, 3)}]
|
||||||
|
batch = trainer.transfer_batch_to_gpu(batch, 0)
|
||||||
|
assert batch[0]['a'].device.index == 0 and batch[0]['a'].type() == 'torch.cuda.FloatTensor'
|
||||||
|
assert batch[0]['b'].device.index == 0 and batch[0]['b'].type() == 'torch.cuda.FloatTensor'
|
||||||
|
|
||||||
|
# tuple of tensor list and list of tensor dict
|
||||||
|
batch = ([torch.rand(2, 3) for _ in range(2)],
|
||||||
|
[{'a': torch.rand(2, 3), 'b': torch.rand(2, 3)} for _ in range(2)])
|
||||||
|
batch = trainer.transfer_batch_to_gpu(batch, 0)
|
||||||
|
assert batch[0][0].device.index == 0 and batch[0][0].type() == 'torch.cuda.FloatTensor'
|
||||||
|
|
||||||
|
assert batch[1][0]['a'].device.index == 0
|
||||||
|
assert batch[1][0]['a'].type() == 'torch.cuda.FloatTensor'
|
||||||
|
|
||||||
|
assert batch[1][0]['b'].device.index == 0
|
||||||
|
assert batch[1][0]['b'].type() == 'torch.cuda.FloatTensor'
|
||||||
|
|
||||||
|
|
||||||
|
def test_simple_cpu():
|
||||||
|
"""
|
||||||
|
Verify continue training session on CPU
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
hparams = testing_utils.get_hparams()
|
||||||
|
model = LightningTestModel(hparams)
|
||||||
|
|
||||||
|
save_dir = testing_utils.init_save_dir()
|
||||||
|
|
||||||
|
# logger file to get meta
|
||||||
|
trainer_options = dict(
|
||||||
|
max_nb_epochs=1,
|
||||||
|
val_percent_check=0.1,
|
||||||
|
train_percent_check=0.1,
|
||||||
|
)
|
||||||
|
|
||||||
|
# fit model
|
||||||
|
trainer = Trainer(**trainer_options)
|
||||||
|
result = trainer.fit(model)
|
||||||
|
|
||||||
|
# traning complete
|
||||||
|
assert result == 1, 'amp + ddp model failed to complete'
|
||||||
|
|
||||||
|
testing_utils.clear_save_dir()
|
||||||
|
|
||||||
|
|
||||||
|
def test_cpu_model():
|
||||||
|
"""
|
||||||
|
Make sure model trains on CPU
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
trainer_options = dict(
|
||||||
|
show_progress_bar=False,
|
||||||
|
logger=testing_utils.get_test_tube_logger(),
|
||||||
|
max_nb_epochs=1,
|
||||||
|
train_percent_check=0.4,
|
||||||
|
val_percent_check=0.4
|
||||||
|
)
|
||||||
|
|
||||||
|
model, hparams = testing_utils.get_model()
|
||||||
|
|
||||||
|
testing_utils.run_gpu_model_test(trainer_options, model, hparams, on_gpu=False)
|
||||||
|
|
||||||
|
|
||||||
|
def test_all_features_cpu_model():
|
||||||
|
"""
|
||||||
|
Test each of the trainer options
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
trainer_options = dict(
|
||||||
|
gradient_clip_val=1.0,
|
||||||
|
overfit_pct=0.20,
|
||||||
|
track_grad_norm=2,
|
||||||
|
print_nan_grads=True,
|
||||||
|
show_progress_bar=False,
|
||||||
|
logger=testing_utils.get_test_tube_logger(),
|
||||||
|
accumulate_grad_batches=2,
|
||||||
|
max_nb_epochs=1,
|
||||||
|
train_percent_check=0.4,
|
||||||
|
val_percent_check=0.4
|
||||||
|
)
|
||||||
|
|
||||||
|
model, hparams = testing_utils.get_model()
|
||||||
|
testing_utils.run_gpu_model_test(trainer_options, model, hparams, on_gpu=False)
|
||||||
|
|
||||||
|
|
||||||
|
def test_tbptt_cpu_model():
|
||||||
|
"""
|
||||||
|
Test truncated back propagation through time works.
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
save_dir = testing_utils.init_save_dir()
|
||||||
|
|
||||||
|
truncated_bptt_steps = 2
|
||||||
|
sequence_size = 30
|
||||||
|
batch_size = 30
|
||||||
|
|
||||||
|
x_seq = torch.rand(batch_size, sequence_size, 1)
|
||||||
|
y_seq_list = torch.rand(batch_size, sequence_size, 1).tolist()
|
||||||
|
|
||||||
|
class MockSeq2SeqDataset(torch.utils.data.Dataset):
|
||||||
|
def __getitem__(self, i):
|
||||||
|
return x_seq, y_seq_list
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return 1
|
||||||
|
|
||||||
|
class BpttTestModel(LightningTestModelBase):
|
||||||
|
def __init__(self, hparams):
|
||||||
|
super().__init__(hparams)
|
||||||
|
self.test_hidden = None
|
||||||
|
|
||||||
|
def training_step(self, batch, batch_idx, hiddens):
|
||||||
|
assert hiddens == self.test_hidden, "Hidden state not persistent between tbptt steps"
|
||||||
|
self.test_hidden = torch.rand(1)
|
||||||
|
|
||||||
|
x_tensor, y_list = batch
|
||||||
|
assert x_tensor.shape[1] == truncated_bptt_steps, "tbptt split Tensor failed"
|
||||||
|
|
||||||
|
y_tensor = torch.tensor(y_list, dtype=x_tensor.dtype)
|
||||||
|
assert y_tensor.shape[1] == truncated_bptt_steps, "tbptt split list failed"
|
||||||
|
|
||||||
|
pred = self.forward(x_tensor.view(batch_size, truncated_bptt_steps))
|
||||||
|
loss_val = torch.nn.functional.mse_loss(
|
||||||
|
pred, y_tensor.view(batch_size, truncated_bptt_steps))
|
||||||
|
return {
|
||||||
|
'loss': loss_val,
|
||||||
|
'hiddens': self.test_hidden,
|
||||||
|
}
|
||||||
|
|
||||||
|
@data_loader
|
||||||
|
def train_dataloader(self):
|
||||||
|
return torch.utils.data.DataLoader(
|
||||||
|
dataset=MockSeq2SeqDataset(),
|
||||||
|
batch_size=batch_size,
|
||||||
|
shuffle=False,
|
||||||
|
sampler=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
trainer_options = dict(
|
||||||
|
max_nb_epochs=1,
|
||||||
|
truncated_bptt_steps=truncated_bptt_steps,
|
||||||
|
val_percent_check=0,
|
||||||
|
weights_summary=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
hparams = testing_utils.get_hparams()
|
||||||
|
hparams.batch_size = batch_size
|
||||||
|
hparams.in_features = truncated_bptt_steps
|
||||||
|
hparams.hidden_dim = truncated_bptt_steps
|
||||||
|
hparams.out_features = truncated_bptt_steps
|
||||||
|
|
||||||
|
model = BpttTestModel(hparams)
|
||||||
|
|
||||||
|
# fit model
|
||||||
|
trainer = Trainer(**trainer_options)
|
||||||
|
result = trainer.fit(model)
|
||||||
|
|
||||||
|
assert result == 1, 'training failed to complete'
|
||||||
|
|
||||||
|
testing_utils.clear_save_dir()
|
||||||
|
|
||||||
|
|
||||||
|
def test_single_gpu_model():
|
||||||
|
"""
|
||||||
|
Make sure single GPU works (DP mode)
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
if not torch.cuda.is_available():
|
||||||
|
warnings.warn('test_single_gpu_model cannot run.'
|
||||||
|
' Rerun on a GPU node to run this test')
|
||||||
|
return
|
||||||
|
model, hparams = testing_utils.get_model()
|
||||||
|
|
||||||
|
trainer_options = dict(
|
||||||
|
show_progress_bar=False,
|
||||||
|
max_nb_epochs=1,
|
||||||
|
train_percent_check=0.1,
|
||||||
|
val_percent_check=0.1,
|
||||||
|
gpus=1
|
||||||
|
)
|
||||||
|
|
||||||
|
testing_utils.run_gpu_model_test(trainer_options, model, hparams)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
pytest.main([__file__])
|
||||||
@@ -0,0 +1,407 @@
|
|||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from pytorch_lightning import Trainer
|
||||||
|
from pytorch_lightning.callbacks import (
|
||||||
|
ModelCheckpoint,
|
||||||
|
)
|
||||||
|
from pytorch_lightning.root_module import memory
|
||||||
|
from pytorch_lightning.testing import (
|
||||||
|
LightningTestModel,
|
||||||
|
)
|
||||||
|
from pytorch_lightning.trainer.dp_mixin import (
|
||||||
|
parse_gpu_ids,
|
||||||
|
determine_root_gpu_device,
|
||||||
|
)
|
||||||
|
from pytorch_lightning.utilities.debugging import MisconfigurationException
|
||||||
|
from . import testing_utils
|
||||||
|
|
||||||
|
PRETEND_N_OF_GPUS = 16
|
||||||
|
|
||||||
|
|
||||||
|
def test_multi_gpu_model_ddp2():
|
||||||
|
"""
|
||||||
|
Make sure DDP2 works
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if not testing_utils.can_run_gpu_test():
|
||||||
|
return
|
||||||
|
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
testing_utils.set_random_master_port()
|
||||||
|
|
||||||
|
model, hparams = testing_utils.get_model()
|
||||||
|
trainer_options = dict(
|
||||||
|
show_progress_bar=True,
|
||||||
|
max_nb_epochs=1,
|
||||||
|
train_percent_check=0.4,
|
||||||
|
val_percent_check=0.2,
|
||||||
|
gpus=2,
|
||||||
|
weights_summary=None,
|
||||||
|
distributed_backend='ddp2'
|
||||||
|
)
|
||||||
|
|
||||||
|
testing_utils.run_gpu_model_test(trainer_options, model, hparams)
|
||||||
|
|
||||||
|
|
||||||
|
def test_multi_gpu_model_ddp():
|
||||||
|
"""
|
||||||
|
Make sure DDP works
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if not testing_utils.can_run_gpu_test():
|
||||||
|
return
|
||||||
|
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
testing_utils.set_random_master_port()
|
||||||
|
|
||||||
|
model, hparams = testing_utils.get_model()
|
||||||
|
trainer_options = dict(
|
||||||
|
show_progress_bar=False,
|
||||||
|
max_nb_epochs=1,
|
||||||
|
train_percent_check=0.4,
|
||||||
|
val_percent_check=0.2,
|
||||||
|
gpus=[0, 1],
|
||||||
|
distributed_backend='ddp'
|
||||||
|
)
|
||||||
|
|
||||||
|
testing_utils.run_gpu_model_test(trainer_options, model, hparams)
|
||||||
|
|
||||||
|
|
||||||
|
def test_optimizer_return_options():
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
trainer = Trainer()
|
||||||
|
model, hparams = testing_utils.get_model()
|
||||||
|
|
||||||
|
# single optimizer
|
||||||
|
opt_a = torch.optim.Adam(model.parameters(), lr=0.002)
|
||||||
|
opt_b = torch.optim.SGD(model.parameters(), lr=0.002)
|
||||||
|
optim, lr_sched = trainer.init_optimizers(opt_a)
|
||||||
|
assert len(optim) == 1 and len(lr_sched) == 0
|
||||||
|
|
||||||
|
# opt tuple
|
||||||
|
opts = (opt_a, opt_b)
|
||||||
|
optim, lr_sched = trainer.init_optimizers(opts)
|
||||||
|
assert len(optim) == 2 and optim[0] == opts[0] and optim[1] == opts[1]
|
||||||
|
assert len(lr_sched) == 0
|
||||||
|
|
||||||
|
# opt list
|
||||||
|
opts = [opt_a, opt_b]
|
||||||
|
optim, lr_sched = trainer.init_optimizers(opts)
|
||||||
|
assert len(optim) == 2 and optim[0] == opts[0] and optim[1] == opts[1]
|
||||||
|
assert len(lr_sched) == 0
|
||||||
|
|
||||||
|
# opt tuple of lists
|
||||||
|
opts = ([opt_a], ['lr_scheduler'])
|
||||||
|
optim, lr_sched = trainer.init_optimizers(opts)
|
||||||
|
assert len(optim) == 1 and len(lr_sched) == 1
|
||||||
|
assert optim[0] == opts[0][0] and lr_sched[0] == 'lr_scheduler'
|
||||||
|
|
||||||
|
|
||||||
|
def test_cpu_slurm_save_load():
|
||||||
|
"""
|
||||||
|
Verify model save/load/checkpoint on CPU
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
hparams = testing_utils.get_hparams()
|
||||||
|
model = LightningTestModel(hparams)
|
||||||
|
|
||||||
|
save_dir = testing_utils.init_save_dir()
|
||||||
|
|
||||||
|
# logger file to get meta
|
||||||
|
logger = testing_utils.get_test_tube_logger(False)
|
||||||
|
|
||||||
|
version = logger.version
|
||||||
|
|
||||||
|
trainer_options = dict(
|
||||||
|
max_nb_epochs=1,
|
||||||
|
logger=logger,
|
||||||
|
checkpoint_callback=ModelCheckpoint(save_dir)
|
||||||
|
)
|
||||||
|
|
||||||
|
# fit model
|
||||||
|
trainer = Trainer(**trainer_options)
|
||||||
|
result = trainer.fit(model)
|
||||||
|
real_global_step = trainer.global_step
|
||||||
|
|
||||||
|
# traning complete
|
||||||
|
assert result == 1, 'amp + ddp model failed to complete'
|
||||||
|
|
||||||
|
# predict with trained model before saving
|
||||||
|
# make a prediction
|
||||||
|
for dataloader in model.test_dataloader():
|
||||||
|
for batch in dataloader:
|
||||||
|
break
|
||||||
|
|
||||||
|
x, y = batch
|
||||||
|
x = x.view(x.size(0), -1)
|
||||||
|
|
||||||
|
model.eval()
|
||||||
|
pred_before_saving = model(x)
|
||||||
|
|
||||||
|
# test HPC saving
|
||||||
|
# simulate snapshot on slurm
|
||||||
|
saved_filepath = trainer.hpc_save(save_dir, logger)
|
||||||
|
assert os.path.exists(saved_filepath)
|
||||||
|
|
||||||
|
# new logger file to get meta
|
||||||
|
logger = testing_utils.get_test_tube_logger(False, version=version)
|
||||||
|
|
||||||
|
trainer_options = dict(
|
||||||
|
max_nb_epochs=1,
|
||||||
|
logger=logger,
|
||||||
|
checkpoint_callback=ModelCheckpoint(save_dir),
|
||||||
|
)
|
||||||
|
trainer = Trainer(**trainer_options)
|
||||||
|
model = LightningTestModel(hparams)
|
||||||
|
|
||||||
|
# set the epoch start hook so we can predict before the model does the full training
|
||||||
|
def assert_pred_same():
|
||||||
|
assert trainer.global_step == real_global_step and trainer.global_step > 0
|
||||||
|
|
||||||
|
# predict with loaded model to make sure answers are the same
|
||||||
|
trainer.model.eval()
|
||||||
|
new_pred = trainer.model(x)
|
||||||
|
assert torch.all(torch.eq(pred_before_saving, new_pred)).item() == 1
|
||||||
|
|
||||||
|
model.on_epoch_start = assert_pred_same
|
||||||
|
|
||||||
|
# by calling fit again, we trigger training, loading weights from the cluster
|
||||||
|
# and our hook to predict using current model before any more weight updates
|
||||||
|
trainer.fit(model)
|
||||||
|
|
||||||
|
testing_utils.clear_save_dir()
|
||||||
|
|
||||||
|
|
||||||
|
def test_multi_gpu_none_backend():
|
||||||
|
"""
|
||||||
|
Make sure when using multiple GPUs the user can't use
|
||||||
|
distributed_backend = None
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
if not testing_utils.can_run_gpu_test():
|
||||||
|
return
|
||||||
|
|
||||||
|
model, hparams = testing_utils.get_model()
|
||||||
|
trainer_options = dict(
|
||||||
|
show_progress_bar=False,
|
||||||
|
max_nb_epochs=1,
|
||||||
|
train_percent_check=0.1,
|
||||||
|
val_percent_check=0.1,
|
||||||
|
gpus='-1'
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(MisconfigurationException):
|
||||||
|
testing_utils.run_gpu_model_test(trainer_options, model, hparams)
|
||||||
|
|
||||||
|
|
||||||
|
def test_multi_gpu_model_dp():
|
||||||
|
"""
|
||||||
|
Make sure DP works
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
if not testing_utils.can_run_gpu_test():
|
||||||
|
return
|
||||||
|
|
||||||
|
model, hparams = testing_utils.get_model()
|
||||||
|
trainer_options = dict(
|
||||||
|
show_progress_bar=False,
|
||||||
|
distributed_backend='dp',
|
||||||
|
max_nb_epochs=1,
|
||||||
|
train_percent_check=0.1,
|
||||||
|
val_percent_check=0.1,
|
||||||
|
gpus='-1'
|
||||||
|
)
|
||||||
|
|
||||||
|
testing_utils.run_gpu_model_test(trainer_options, model, hparams)
|
||||||
|
|
||||||
|
# test memory helper functions
|
||||||
|
memory.get_memory_profile('min_max')
|
||||||
|
|
||||||
|
|
||||||
|
def test_ddp_sampler_error():
|
||||||
|
"""
|
||||||
|
Make sure DDP + AMP work
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if not testing_utils.can_run_gpu_test():
|
||||||
|
return
|
||||||
|
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
testing_utils.set_random_master_port()
|
||||||
|
|
||||||
|
hparams = testing_utils.get_hparams()
|
||||||
|
model = LightningTestModel(hparams, force_remove_distributed_sampler=True)
|
||||||
|
|
||||||
|
logger = testing_utils.get_test_tube_logger(True)
|
||||||
|
|
||||||
|
trainer = Trainer(
|
||||||
|
logger=logger,
|
||||||
|
show_progress_bar=False,
|
||||||
|
max_nb_epochs=1,
|
||||||
|
gpus=[0, 1],
|
||||||
|
distributed_backend='ddp',
|
||||||
|
use_amp=True
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.warns(UserWarning):
|
||||||
|
trainer.get_dataloaders(model)
|
||||||
|
|
||||||
|
testing_utils.clear_save_dir()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mocked_device_count(monkeypatch):
|
||||||
|
def device_count():
|
||||||
|
return PRETEND_N_OF_GPUS
|
||||||
|
|
||||||
|
monkeypatch.setattr(torch.cuda, 'device_count', device_count)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mocked_device_count_0(monkeypatch):
|
||||||
|
def device_count():
|
||||||
|
return 0
|
||||||
|
|
||||||
|
monkeypatch.setattr(torch.cuda, 'device_count', device_count)
|
||||||
|
|
||||||
|
|
||||||
|
test_num_gpus_data = [
|
||||||
|
pytest.param(None, 0, None, id="None - expect 0 gpu to use."),
|
||||||
|
pytest.param(0, 0, None, id="Oth gpu, expect 1 gpu to use."),
|
||||||
|
pytest.param(1, 1, None, id="1st gpu, expect 1 gpu to use."),
|
||||||
|
pytest.param(-1, PRETEND_N_OF_GPUS, "ddp", id="-1 - use all gpus"),
|
||||||
|
pytest.param('-1', PRETEND_N_OF_GPUS, "ddp", id="'-1' - use all gpus"),
|
||||||
|
pytest.param(3, 3, "ddp", id="3rd gpu - 1 gpu to use (backend:ddp)")
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gpus_param_tests
|
||||||
|
@pytest.mark.parametrize(["gpus", "expected_num_gpus", "distributed_backend"], test_num_gpus_data)
|
||||||
|
def test_trainer_gpu_parse(mocked_device_count, gpus, expected_num_gpus, distributed_backend):
|
||||||
|
assert Trainer(gpus=gpus, distributed_backend=distributed_backend).num_gpus == expected_num_gpus
|
||||||
|
|
||||||
|
|
||||||
|
test_num_gpus_data_0 = [
|
||||||
|
pytest.param(None, 0, None, id="None - expect 0 gpu to use."),
|
||||||
|
pytest.param(None, 0, "ddp", id="None - expect 0 gpu to use."),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gpus_param_tests
|
||||||
|
@pytest.mark.parametrize(["gpus", "expected_num_gpus", "distributed_backend"], test_num_gpus_data_0)
|
||||||
|
def test_trainer_num_gpu_0(mocked_device_count_0, gpus, expected_num_gpus, distributed_backend):
|
||||||
|
assert Trainer(gpus=gpus, distributed_backend=distributed_backend).num_gpus == expected_num_gpus
|
||||||
|
|
||||||
|
|
||||||
|
test_root_gpu_data = [
|
||||||
|
pytest.param(None, None, "ddp", id="None is None"),
|
||||||
|
pytest.param(0, None, "ddp", id="O gpus, expect gpu root device to be None."),
|
||||||
|
pytest.param(1, 0, "ddp", id="1 gpu, expect gpu root device to be 0."),
|
||||||
|
pytest.param(-1, 0, "ddp", id="-1 - use all gpus, expect gpu root device to be 0."),
|
||||||
|
pytest.param('-1', 0, "ddp", id="'-1' - use all gpus, expect gpu root device to be 0."),
|
||||||
|
pytest.param(3, 0, "ddp", id="3 gpus, expect gpu root device to be 0.(backend:ddp)")]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gpus_param_tests
|
||||||
|
@pytest.mark.parametrize(['gpus', 'expected_root_gpu', "distributed_backend"], test_root_gpu_data)
|
||||||
|
def test_root_gpu_property(mocked_device_count, gpus, expected_root_gpu, distributed_backend):
|
||||||
|
assert Trainer(gpus=gpus, distributed_backend=distributed_backend).root_gpu == expected_root_gpu
|
||||||
|
|
||||||
|
|
||||||
|
test_root_gpu_data_for_0_devices_passing = [
|
||||||
|
pytest.param(None, None, None, id="None is None"),
|
||||||
|
pytest.param(None, None, "ddp", id="None is None"),
|
||||||
|
pytest.param(0, None, "ddp", id="None is None"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gpus_param_tests
|
||||||
|
@pytest.mark.parametrize([
|
||||||
|
'gpus', 'expected_root_gpu', "distributed_backend"], test_root_gpu_data_for_0_devices_passing)
|
||||||
|
def test_root_gpu_property_0_passing(
|
||||||
|
mocked_device_count_0, gpus, expected_root_gpu, distributed_backend):
|
||||||
|
assert Trainer(gpus=gpus, distributed_backend=distributed_backend).root_gpu == expected_root_gpu
|
||||||
|
|
||||||
|
|
||||||
|
# Asking for a gpu when non are available will result in a MisconfigurationException
|
||||||
|
test_root_gpu_data_for_0_devices_raising = [
|
||||||
|
pytest.param(1, None, "ddp"),
|
||||||
|
pytest.param(3, None, "ddp"),
|
||||||
|
pytest.param(3, None, "ddp"),
|
||||||
|
pytest.param([1, 2], None, "ddp"),
|
||||||
|
pytest.param([0, 1], None, "ddp"),
|
||||||
|
pytest.param(-1, None, "ddp"),
|
||||||
|
pytest.param('-1', None, "ddp")
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gpus_param_tests
|
||||||
|
@pytest.mark.parametrize([
|
||||||
|
'gpus', 'expected_root_gpu', "distributed_backend"], test_root_gpu_data_for_0_devices_raising)
|
||||||
|
def test_root_gpu_property_0_raising(
|
||||||
|
mocked_device_count_0, gpus, expected_root_gpu, distributed_backend):
|
||||||
|
with pytest.raises(MisconfigurationException):
|
||||||
|
Trainer(gpus=gpus, distributed_backend=distributed_backend).root_gpu
|
||||||
|
|
||||||
|
|
||||||
|
test_determine_root_gpu_device_data = [
|
||||||
|
pytest.param(None, None, id="No gpus, expect gpu root device to be None"),
|
||||||
|
pytest.param([0], 0, id="Oth gpu, expect gpu root device to be 0."),
|
||||||
|
pytest.param([1], 1, id="1st gpu, expect gpu root device to be 1."),
|
||||||
|
pytest.param([3], 3, id="3rd gpu, expect gpu root device to be 3."),
|
||||||
|
pytest.param([1, 2], 1, id="[1, 2] gpus, expect gpu root device to be 1."),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gpus_param_tests
|
||||||
|
@pytest.mark.parametrize(['gpus', 'expected_root_gpu'], test_determine_root_gpu_device_data)
|
||||||
|
def test_determine_root_gpu_device(gpus, expected_root_gpu):
|
||||||
|
assert determine_root_gpu_device(gpus) == expected_root_gpu
|
||||||
|
|
||||||
|
|
||||||
|
test_parse_gpu_ids_data = [
|
||||||
|
pytest.param(None, None),
|
||||||
|
pytest.param(0, None),
|
||||||
|
pytest.param(1, [0]),
|
||||||
|
pytest.param(-1, list(range(PRETEND_N_OF_GPUS)), id="-1 - use all gpus"),
|
||||||
|
pytest.param('-1', list(range(PRETEND_N_OF_GPUS)), id="'-1' - use all gpus"),
|
||||||
|
pytest.param(3, [0, 1, 2])]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gpus_param_tests
|
||||||
|
@pytest.mark.parametrize(['gpus', 'expected_gpu_ids'], test_parse_gpu_ids_data)
|
||||||
|
def test_parse_gpu_ids(mocked_device_count, gpus, expected_gpu_ids):
|
||||||
|
assert parse_gpu_ids(gpus) == expected_gpu_ids
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gpus_param_tests
|
||||||
|
@pytest.mark.parametrize("gpus", [[1, 2, 19], -1, '-1'])
|
||||||
|
def test_parse_gpu_fail_on_non_existant_id(mocked_device_count_0, gpus):
|
||||||
|
with pytest.raises(MisconfigurationException):
|
||||||
|
parse_gpu_ids(gpus)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gpus_param_tests
|
||||||
|
def test_parse_gpu_fail_on_non_existant_id_2(mocked_device_count):
|
||||||
|
with pytest.raises(MisconfigurationException):
|
||||||
|
parse_gpu_ids([1, 2, 19])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gpus_param_tests
|
||||||
|
@pytest.mark.parametrize("gpus", [-1, '-1'])
|
||||||
|
def test_parse_gpu_returns_None_when_no_devices_are_available(mocked_device_count_0, gpus):
|
||||||
|
with pytest.raises(MisconfigurationException):
|
||||||
|
parse_gpu_ids(gpus)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
pytest.main([__file__])
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,324 @@
|
|||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from pytorch_lightning import Trainer
|
||||||
|
from pytorch_lightning.callbacks import (
|
||||||
|
ModelCheckpoint,
|
||||||
|
)
|
||||||
|
from pytorch_lightning.testing import (
|
||||||
|
LightningTestModel,
|
||||||
|
LightningTestModelBase,
|
||||||
|
LightningValidationStepMixin,
|
||||||
|
LightningValidationMultipleDataloadersMixin,
|
||||||
|
LightningTestMixin,
|
||||||
|
LightningTestMultipleDataloadersMixin,
|
||||||
|
)
|
||||||
|
from pytorch_lightning.trainer import trainer_io
|
||||||
|
from pytorch_lightning.trainer.logging_mixin import TrainerLoggingMixin
|
||||||
|
from . import testing_utils
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_val_module():
|
||||||
|
"""
|
||||||
|
Tests use case where trainer saves the model, and user loads it from tags independently
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
hparams = testing_utils.get_hparams()
|
||||||
|
|
||||||
|
class CurrentTestModel(LightningTestModelBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
model = CurrentTestModel(hparams)
|
||||||
|
|
||||||
|
save_dir = testing_utils.init_save_dir()
|
||||||
|
|
||||||
|
# logger file to get meta
|
||||||
|
logger = testing_utils.get_test_tube_logger(False)
|
||||||
|
|
||||||
|
trainer_options = dict(
|
||||||
|
max_nb_epochs=1,
|
||||||
|
logger=logger,
|
||||||
|
checkpoint_callback=ModelCheckpoint(save_dir)
|
||||||
|
)
|
||||||
|
|
||||||
|
# fit model
|
||||||
|
trainer = Trainer(**trainer_options)
|
||||||
|
result = trainer.fit(model)
|
||||||
|
|
||||||
|
# training complete
|
||||||
|
assert result == 1, 'amp + ddp model failed to complete'
|
||||||
|
|
||||||
|
# save model
|
||||||
|
new_weights_path = os.path.join(save_dir, 'save_test.ckpt')
|
||||||
|
trainer.save_checkpoint(new_weights_path)
|
||||||
|
|
||||||
|
# load new model
|
||||||
|
tags_path = logger.experiment.get_data_path(logger.experiment.name, logger.experiment.version)
|
||||||
|
tags_path = os.path.join(tags_path, 'meta_tags.csv')
|
||||||
|
model_2 = LightningTestModel.load_from_metrics(weights_path=new_weights_path,
|
||||||
|
tags_csv=tags_path)
|
||||||
|
model_2.eval()
|
||||||
|
|
||||||
|
# make prediction
|
||||||
|
testing_utils.clear_save_dir()
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_val_end_module():
|
||||||
|
"""
|
||||||
|
Tests use case where trainer saves the model, and user loads it from tags independently
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
class CurrentTestModel(LightningValidationStepMixin, LightningTestModelBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
hparams = testing_utils.get_hparams()
|
||||||
|
model = CurrentTestModel(hparams)
|
||||||
|
|
||||||
|
save_dir = testing_utils.init_save_dir()
|
||||||
|
|
||||||
|
# logger file to get meta
|
||||||
|
logger = testing_utils.get_test_tube_logger(False)
|
||||||
|
|
||||||
|
trainer_options = dict(
|
||||||
|
max_nb_epochs=1,
|
||||||
|
logger=logger,
|
||||||
|
checkpoint_callback=ModelCheckpoint(save_dir)
|
||||||
|
)
|
||||||
|
|
||||||
|
# fit model
|
||||||
|
trainer = Trainer(**trainer_options)
|
||||||
|
result = trainer.fit(model)
|
||||||
|
|
||||||
|
# traning complete
|
||||||
|
assert result == 1, 'amp + ddp model failed to complete'
|
||||||
|
|
||||||
|
# save model
|
||||||
|
new_weights_path = os.path.join(save_dir, 'save_test.ckpt')
|
||||||
|
trainer.save_checkpoint(new_weights_path)
|
||||||
|
|
||||||
|
# load new model
|
||||||
|
tags_path = logger.experiment.get_data_path(logger.experiment.name, logger.experiment.version)
|
||||||
|
tags_path = os.path.join(tags_path, 'meta_tags.csv')
|
||||||
|
model_2 = LightningTestModel.load_from_metrics(weights_path=new_weights_path,
|
||||||
|
tags_csv=tags_path)
|
||||||
|
model_2.eval()
|
||||||
|
|
||||||
|
# make prediction
|
||||||
|
testing_utils.clear_save_dir()
|
||||||
|
|
||||||
|
|
||||||
|
def test_gradient_accumulation_scheduling():
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
"""
|
||||||
|
Test grad accumulation by the freq of optimizer updates
|
||||||
|
"""
|
||||||
|
# test incorrect configs
|
||||||
|
with pytest.raises(IndexError):
|
||||||
|
assert Trainer(accumulate_grad_batches={0: 3, 1: 4, 4: 6})
|
||||||
|
assert Trainer(accumulate_grad_batches={-2: 3})
|
||||||
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
assert Trainer(accumulate_grad_batches={})
|
||||||
|
assert Trainer(accumulate_grad_batches=[[2, 3], [4, 6]])
|
||||||
|
assert Trainer(accumulate_grad_batches={1: 2, 3.: 4})
|
||||||
|
assert Trainer(accumulate_grad_batches={1: 2.5, 3: 5})
|
||||||
|
|
||||||
|
# test optimizer call freq matches scheduler
|
||||||
|
def optimizer_step(self, epoch_nb, batch_nb, optimizer, optimizer_i, second_order_closure=None):
|
||||||
|
# only test the first 12 batches in epoch
|
||||||
|
if batch_nb < 12:
|
||||||
|
if epoch_nb == 0:
|
||||||
|
# reset counter when starting epoch
|
||||||
|
if batch_nb == 0:
|
||||||
|
self.prev_called_batch_nb = 0
|
||||||
|
|
||||||
|
# use this opportunity to test once
|
||||||
|
assert self.trainer.accumulate_grad_batches == 1
|
||||||
|
|
||||||
|
assert batch_nb == self.prev_called_batch_nb
|
||||||
|
self.prev_called_batch_nb += 1
|
||||||
|
|
||||||
|
elif 1 <= epoch_nb <= 2:
|
||||||
|
# reset counter when starting epoch
|
||||||
|
if batch_nb == 1:
|
||||||
|
self.prev_called_batch_nb = 1
|
||||||
|
|
||||||
|
# use this opportunity to test once
|
||||||
|
assert self.trainer.accumulate_grad_batches == 2
|
||||||
|
|
||||||
|
assert batch_nb == self.prev_called_batch_nb
|
||||||
|
self.prev_called_batch_nb += 2
|
||||||
|
|
||||||
|
else:
|
||||||
|
if batch_nb == 3:
|
||||||
|
self.prev_called_batch_nb = 3
|
||||||
|
|
||||||
|
# use this opportunity to test once
|
||||||
|
assert self.trainer.accumulate_grad_batches == 4
|
||||||
|
|
||||||
|
assert batch_nb == self.prev_called_batch_nb
|
||||||
|
self.prev_called_batch_nb += 3
|
||||||
|
|
||||||
|
optimizer.step()
|
||||||
|
|
||||||
|
# clear gradients
|
||||||
|
optimizer.zero_grad()
|
||||||
|
|
||||||
|
hparams = testing_utils.get_hparams()
|
||||||
|
model = LightningTestModel(hparams)
|
||||||
|
schedule = {1: 2, 3: 4}
|
||||||
|
|
||||||
|
trainer = Trainer(accumulate_grad_batches=schedule,
|
||||||
|
train_percent_check=0.1,
|
||||||
|
val_percent_check=0.1,
|
||||||
|
max_nb_epochs=4)
|
||||||
|
|
||||||
|
# for the test
|
||||||
|
trainer.optimizer_step = optimizer_step
|
||||||
|
model.prev_called_batch_nb = 0
|
||||||
|
|
||||||
|
trainer.fit(model)
|
||||||
|
|
||||||
|
|
||||||
|
def test_loading_meta_tags():
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
from argparse import Namespace
|
||||||
|
hparams = testing_utils.get_hparams()
|
||||||
|
|
||||||
|
# save tags
|
||||||
|
logger = testing_utils.get_test_tube_logger(False)
|
||||||
|
logger.log_hyperparams(Namespace(some_str='a_str', an_int=1, a_float=2.0))
|
||||||
|
logger.log_hyperparams(hparams)
|
||||||
|
logger.save()
|
||||||
|
|
||||||
|
# load tags
|
||||||
|
tags_path = logger.experiment.get_data_path(
|
||||||
|
logger.experiment.name, logger.experiment.version
|
||||||
|
) + '/meta_tags.csv'
|
||||||
|
tags = trainer_io.load_hparams_from_tags_csv(tags_path)
|
||||||
|
|
||||||
|
assert tags.batch_size == 32 and tags.hidden_dim == 1000
|
||||||
|
|
||||||
|
testing_utils.clear_save_dir()
|
||||||
|
|
||||||
|
|
||||||
|
def test_dp_output_reduce():
|
||||||
|
mixin = TrainerLoggingMixin()
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
# test identity when we have a single gpu
|
||||||
|
out = torch.rand(3, 1)
|
||||||
|
assert mixin.reduce_distributed_output(out, nb_gpus=1) is out
|
||||||
|
|
||||||
|
# average when we have multiples
|
||||||
|
assert mixin.reduce_distributed_output(out, nb_gpus=2) == out.mean()
|
||||||
|
|
||||||
|
# when we have a dict of vals
|
||||||
|
out = {
|
||||||
|
'a': out,
|
||||||
|
'b': {
|
||||||
|
'c': out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reduced = mixin.reduce_distributed_output(out, nb_gpus=3)
|
||||||
|
assert reduced['a'] == out['a']
|
||||||
|
assert reduced['b']['c'] == out['b']['c']
|
||||||
|
|
||||||
|
|
||||||
|
def test_model_freeze_unfreeze():
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
hparams = testing_utils.get_hparams()
|
||||||
|
model = LightningTestModel(hparams)
|
||||||
|
|
||||||
|
model.freeze()
|
||||||
|
model.unfreeze()
|
||||||
|
|
||||||
|
|
||||||
|
def test_multiple_val_dataloader():
|
||||||
|
"""
|
||||||
|
Verify multiple val_dataloader
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
class CurrentTestModel(
|
||||||
|
LightningValidationMultipleDataloadersMixin,
|
||||||
|
LightningTestModelBase
|
||||||
|
):
|
||||||
|
pass
|
||||||
|
|
||||||
|
hparams = testing_utils.get_hparams()
|
||||||
|
model = CurrentTestModel(hparams)
|
||||||
|
|
||||||
|
# logger file to get meta
|
||||||
|
trainer_options = dict(
|
||||||
|
max_nb_epochs=1,
|
||||||
|
val_percent_check=0.1,
|
||||||
|
train_percent_check=1.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
# fit model
|
||||||
|
trainer = Trainer(**trainer_options)
|
||||||
|
result = trainer.fit(model)
|
||||||
|
|
||||||
|
# verify training completed
|
||||||
|
assert result == 1
|
||||||
|
|
||||||
|
# verify there are 2 val loaders
|
||||||
|
assert len(trainer.get_val_dataloaders()) == 2, \
|
||||||
|
'Multiple val_dataloaders not initiated properly'
|
||||||
|
|
||||||
|
# make sure predictions are good for each val set
|
||||||
|
for dataloader in trainer.get_val_dataloaders():
|
||||||
|
testing_utils.run_prediction(dataloader, trainer.model)
|
||||||
|
|
||||||
|
|
||||||
|
def test_multiple_test_dataloader():
|
||||||
|
"""
|
||||||
|
Verify multiple test_dataloader
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
class CurrentTestModel(
|
||||||
|
LightningTestMultipleDataloadersMixin,
|
||||||
|
LightningTestModelBase
|
||||||
|
):
|
||||||
|
pass
|
||||||
|
|
||||||
|
hparams = testing_utils.get_hparams()
|
||||||
|
model = CurrentTestModel(hparams)
|
||||||
|
|
||||||
|
# logger file to get meta
|
||||||
|
trainer_options = dict(
|
||||||
|
max_nb_epochs=1,
|
||||||
|
val_percent_check=0.1,
|
||||||
|
train_percent_check=0.1,
|
||||||
|
)
|
||||||
|
|
||||||
|
# fit model
|
||||||
|
trainer = Trainer(**trainer_options)
|
||||||
|
result = trainer.fit(model)
|
||||||
|
|
||||||
|
# verify there are 2 val loaders
|
||||||
|
assert len(trainer.get_test_dataloaders()) == 2, \
|
||||||
|
'Multiple test_dataloaders not initiated properly'
|
||||||
|
|
||||||
|
# make sure predictions are good for each test set
|
||||||
|
for dataloader in trainer.get_test_dataloaders():
|
||||||
|
testing_utils.run_prediction(dataloader, trainer.model)
|
||||||
|
|
||||||
|
# run the test method
|
||||||
|
trainer.test()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
pytest.main([__file__])
|
||||||
@@ -1,14 +1,18 @@
|
|||||||
import os.path
|
import os
|
||||||
import pickle
|
import pickle
|
||||||
import shutil
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
from pytorch_lightning import Trainer
|
from pytorch_lightning import Trainer
|
||||||
|
|
||||||
from pytorch_lightning.testing import LightningTestModel
|
from pytorch_lightning.testing import LightningTestModel
|
||||||
from .test_models import get_hparams, get_test_tube_logger, init_save_dir, clear_save_dir
|
from pytorch_lightning.logging import LightningLoggerBase, rank_zero_only
|
||||||
|
from . import testing_utils
|
||||||
|
|
||||||
|
RANDOM_FILE_PATHS = list(np.random.randint(12000, 19000, 1000))
|
||||||
|
ROOT_SEED = 1234
|
||||||
|
torch.manual_seed(ROOT_SEED)
|
||||||
|
np.random.seed(ROOT_SEED)
|
||||||
RANDOM_SEEDS = list(np.random.randint(0, 10000, 1000))
|
RANDOM_SEEDS = list(np.random.randint(0, 10000, 1000))
|
||||||
|
|
||||||
|
|
||||||
@@ -17,13 +21,12 @@ def test_testtube_logger():
|
|||||||
verify that basic functionality of test tube logger works
|
verify that basic functionality of test tube logger works
|
||||||
"""
|
"""
|
||||||
reset_seed()
|
reset_seed()
|
||||||
|
hparams = testing_utils.get_hparams()
|
||||||
hparams = get_hparams()
|
|
||||||
model = LightningTestModel(hparams)
|
model = LightningTestModel(hparams)
|
||||||
|
|
||||||
save_dir = init_save_dir()
|
save_dir = testing_utils.init_save_dir()
|
||||||
|
|
||||||
logger = get_test_tube_logger(False)
|
logger = testing_utils.get_test_tube_logger(False)
|
||||||
|
|
||||||
trainer_options = dict(
|
trainer_options = dict(
|
||||||
max_nb_epochs=1,
|
max_nb_epochs=1,
|
||||||
@@ -36,7 +39,7 @@ def test_testtube_logger():
|
|||||||
|
|
||||||
assert result == 1, "Training failed"
|
assert result == 1, "Training failed"
|
||||||
|
|
||||||
clear_save_dir()
|
testing_utils.clear_save_dir()
|
||||||
|
|
||||||
|
|
||||||
def test_testtube_pickle():
|
def test_testtube_pickle():
|
||||||
@@ -45,12 +48,12 @@ def test_testtube_pickle():
|
|||||||
"""
|
"""
|
||||||
reset_seed()
|
reset_seed()
|
||||||
|
|
||||||
hparams = get_hparams()
|
hparams = testing_utils.get_hparams()
|
||||||
model = LightningTestModel(hparams)
|
model = LightningTestModel(hparams)
|
||||||
|
|
||||||
save_dir = init_save_dir()
|
save_dir = testing_utils.init_save_dir()
|
||||||
|
|
||||||
logger = get_test_tube_logger(False)
|
logger = testing_utils.get_test_tube_logger(False)
|
||||||
logger.log_hyperparams(hparams)
|
logger.log_hyperparams(hparams)
|
||||||
logger.save()
|
logger.save()
|
||||||
|
|
||||||
@@ -65,6 +68,8 @@ def test_testtube_pickle():
|
|||||||
trainer2 = pickle.loads(pkl_bytes)
|
trainer2 = pickle.loads(pkl_bytes)
|
||||||
trainer2.logger.log_metrics({"acc": 1.0})
|
trainer2.logger.log_metrics({"acc": 1.0})
|
||||||
|
|
||||||
|
testing_utils.clear_save_dir()
|
||||||
|
|
||||||
|
|
||||||
def test_mlflow_logger():
|
def test_mlflow_logger():
|
||||||
"""
|
"""
|
||||||
@@ -77,15 +82,13 @@ def test_mlflow_logger():
|
|||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
return
|
return
|
||||||
|
|
||||||
hparams = get_hparams()
|
hparams = testing_utils.get_hparams()
|
||||||
model = LightningTestModel(hparams)
|
model = LightningTestModel(hparams)
|
||||||
|
|
||||||
root_dir = os.path.dirname(os.path.realpath(__file__))
|
root_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
mlflow_dir = os.path.join(root_dir, "mlruns")
|
mlflow_dir = os.path.join(root_dir, "mlruns")
|
||||||
|
|
||||||
logger = MLFlowLogger("test", f"file://{mlflow_dir}")
|
logger = MLFlowLogger("test", f"file://{mlflow_dir}")
|
||||||
logger.log_hyperparams(hparams)
|
|
||||||
logger.save()
|
|
||||||
|
|
||||||
trainer_options = dict(
|
trainer_options = dict(
|
||||||
max_nb_epochs=1,
|
max_nb_epochs=1,
|
||||||
@@ -96,10 +99,10 @@ def test_mlflow_logger():
|
|||||||
trainer = Trainer(**trainer_options)
|
trainer = Trainer(**trainer_options)
|
||||||
result = trainer.fit(model)
|
result = trainer.fit(model)
|
||||||
|
|
||||||
|
print('result finished')
|
||||||
assert result == 1, "Training failed"
|
assert result == 1, "Training failed"
|
||||||
|
|
||||||
n = np.random.randint(0, 10000000, 1)[0]
|
testing_utils.clear_save_dir()
|
||||||
shutil.move(mlflow_dir, mlflow_dir + f'_{n}')
|
|
||||||
|
|
||||||
|
|
||||||
def test_mlflow_pickle():
|
def test_mlflow_pickle():
|
||||||
@@ -113,15 +116,13 @@ def test_mlflow_pickle():
|
|||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
return
|
return
|
||||||
|
|
||||||
hparams = get_hparams()
|
hparams = testing_utils.get_hparams()
|
||||||
model = LightningTestModel(hparams)
|
model = LightningTestModel(hparams)
|
||||||
|
|
||||||
root_dir = os.path.dirname(os.path.realpath(__file__))
|
root_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
mlflow_dir = os.path.join(root_dir, "mlruns")
|
mlflow_dir = os.path.join(root_dir, "mlruns")
|
||||||
|
|
||||||
logger = MLFlowLogger("test", f"file://{mlflow_dir}")
|
logger = MLFlowLogger("test", f"file://{mlflow_dir}")
|
||||||
logger.log_hyperparams(hparams)
|
|
||||||
logger.save()
|
|
||||||
|
|
||||||
trainer_options = dict(
|
trainer_options = dict(
|
||||||
max_nb_epochs=1,
|
max_nb_epochs=1,
|
||||||
@@ -133,6 +134,57 @@ def test_mlflow_pickle():
|
|||||||
trainer2 = pickle.loads(pkl_bytes)
|
trainer2 = pickle.loads(pkl_bytes)
|
||||||
trainer2.logger.log_metrics({"acc": 1.0})
|
trainer2.logger.log_metrics({"acc": 1.0})
|
||||||
|
|
||||||
|
testing_utils.clear_save_dir()
|
||||||
|
|
||||||
|
|
||||||
|
def test_custom_logger(tmpdir):
|
||||||
|
|
||||||
|
class CustomLogger(LightningLoggerBase):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.hparams_logged = None
|
||||||
|
self.metrics_logged = None
|
||||||
|
self.finalized = False
|
||||||
|
|
||||||
|
@rank_zero_only
|
||||||
|
def log_hyperparams(self, params):
|
||||||
|
self.hparams_logged = params
|
||||||
|
|
||||||
|
@rank_zero_only
|
||||||
|
def log_metrics(self, metrics, step_num):
|
||||||
|
self.metrics_logged = metrics
|
||||||
|
|
||||||
|
@rank_zero_only
|
||||||
|
def finalize(self, status):
|
||||||
|
self.finalized_status = status
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return "name"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def version(self):
|
||||||
|
return "1"
|
||||||
|
|
||||||
|
hparams = testing_utils.get_hparams()
|
||||||
|
model = LightningTestModel(hparams)
|
||||||
|
|
||||||
|
logger = CustomLogger()
|
||||||
|
|
||||||
|
trainer_options = dict(
|
||||||
|
max_nb_epochs=1,
|
||||||
|
train_percent_check=0.01,
|
||||||
|
logger=logger,
|
||||||
|
default_save_path=tmpdir
|
||||||
|
)
|
||||||
|
|
||||||
|
trainer = Trainer(**trainer_options)
|
||||||
|
result = trainer.fit(model)
|
||||||
|
assert result == 1, "Training failed"
|
||||||
|
assert logger.hparams_logged == hparams
|
||||||
|
assert logger.metrics_logged != {}
|
||||||
|
assert logger.finalized_status == "success"
|
||||||
|
|
||||||
|
|
||||||
def reset_seed():
|
def reset_seed():
|
||||||
SEED = RANDOM_SEEDS.pop()
|
SEED = RANDOM_SEEDS.pop()
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
import os
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from pytorch_lightning import Trainer
|
||||||
|
from pytorch_lightning.testing import (
|
||||||
|
LightningTestModel,
|
||||||
|
)
|
||||||
|
from pytorch_lightning.utilities.debugging import MisconfigurationException
|
||||||
|
from . import testing_utils
|
||||||
|
|
||||||
|
|
||||||
|
def test_amp_single_gpu():
|
||||||
|
"""
|
||||||
|
Make sure DDP + AMP work
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
if not testing_utils.can_run_gpu_test():
|
||||||
|
return
|
||||||
|
|
||||||
|
hparams = testing_utils.get_hparams()
|
||||||
|
model = LightningTestModel(hparams)
|
||||||
|
|
||||||
|
trainer_options = dict(
|
||||||
|
show_progress_bar=True,
|
||||||
|
max_nb_epochs=1,
|
||||||
|
gpus=1,
|
||||||
|
distributed_backend='ddp',
|
||||||
|
use_amp=True
|
||||||
|
)
|
||||||
|
|
||||||
|
testing_utils.run_gpu_model_test(trainer_options, model, hparams)
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_amp_single_gpu():
|
||||||
|
"""
|
||||||
|
Make sure DDP + AMP work
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
if not testing_utils.can_run_gpu_test():
|
||||||
|
return
|
||||||
|
|
||||||
|
hparams = testing_utils.get_hparams()
|
||||||
|
model = LightningTestModel(hparams)
|
||||||
|
|
||||||
|
trainer_options = dict(
|
||||||
|
show_progress_bar=True,
|
||||||
|
max_nb_epochs=1,
|
||||||
|
gpus=1,
|
||||||
|
distributed_backend='dp',
|
||||||
|
use_amp=True
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises((MisconfigurationException, ModuleNotFoundError)):
|
||||||
|
testing_utils.run_gpu_model_test(trainer_options, model, hparams)
|
||||||
|
|
||||||
|
|
||||||
|
def test_amp_gpu_ddp():
|
||||||
|
"""
|
||||||
|
Make sure DDP + AMP work
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if not testing_utils.can_run_gpu_test():
|
||||||
|
return
|
||||||
|
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
testing_utils.set_random_master_port()
|
||||||
|
|
||||||
|
hparams = testing_utils.get_hparams()
|
||||||
|
model = LightningTestModel(hparams)
|
||||||
|
|
||||||
|
trainer_options = dict(
|
||||||
|
show_progress_bar=True,
|
||||||
|
max_nb_epochs=1,
|
||||||
|
gpus=2,
|
||||||
|
distributed_backend='ddp',
|
||||||
|
use_amp=True
|
||||||
|
)
|
||||||
|
|
||||||
|
testing_utils.run_gpu_model_test(trainer_options, model, hparams)
|
||||||
|
|
||||||
|
|
||||||
|
def test_amp_gpu_ddp_slurm_managed():
|
||||||
|
"""
|
||||||
|
Make sure DDP + AMP work
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if not testing_utils.can_run_gpu_test():
|
||||||
|
return
|
||||||
|
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
# simulate setting slurm flags
|
||||||
|
testing_utils.set_random_master_port()
|
||||||
|
os.environ['SLURM_LOCALID'] = str(0)
|
||||||
|
|
||||||
|
hparams = testing_utils.get_hparams()
|
||||||
|
model = LightningTestModel(hparams)
|
||||||
|
|
||||||
|
trainer_options = dict(
|
||||||
|
show_progress_bar=True,
|
||||||
|
max_nb_epochs=1,
|
||||||
|
gpus=[0],
|
||||||
|
distributed_backend='ddp',
|
||||||
|
use_amp=True
|
||||||
|
)
|
||||||
|
|
||||||
|
save_dir = testing_utils.init_save_dir()
|
||||||
|
|
||||||
|
# exp file to get meta
|
||||||
|
logger = testing_utils.get_test_tube_logger(False)
|
||||||
|
|
||||||
|
# exp file to get weights
|
||||||
|
checkpoint = testing_utils.init_checkpoint_callback(logger)
|
||||||
|
|
||||||
|
# add these to the trainer options
|
||||||
|
trainer_options['checkpoint_callback'] = checkpoint
|
||||||
|
trainer_options['logger'] = logger
|
||||||
|
|
||||||
|
# fit model
|
||||||
|
trainer = Trainer(**trainer_options)
|
||||||
|
trainer.is_slurm_managing_tasks = True
|
||||||
|
result = trainer.fit(model)
|
||||||
|
|
||||||
|
# correct result and ok accuracy
|
||||||
|
assert result == 1, 'amp + ddp model failed to complete'
|
||||||
|
|
||||||
|
# test root model address
|
||||||
|
assert trainer.resolve_root_node_address('abc') == 'abc'
|
||||||
|
assert trainer.resolve_root_node_address('abc[23]') == 'abc23'
|
||||||
|
assert trainer.resolve_root_node_address('abc[23-24]') == 'abc23'
|
||||||
|
assert trainer.resolve_root_node_address('abc[23-24, 45-40, 40]') == 'abc23'
|
||||||
|
|
||||||
|
# test model loading with a map_location
|
||||||
|
pretrained_model = testing_utils.load_model(logger.experiment,
|
||||||
|
trainer.checkpoint_callback.filepath)
|
||||||
|
|
||||||
|
# test model preds
|
||||||
|
for dataloader in trainer.get_test_dataloaders():
|
||||||
|
testing_utils.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(save_dir, logger)
|
||||||
|
trainer.hpc_load(save_dir, on_gpu=True)
|
||||||
|
|
||||||
|
# test freeze on gpu
|
||||||
|
model.freeze()
|
||||||
|
model.unfreeze()
|
||||||
|
|
||||||
|
testing_utils.clear_save_dir()
|
||||||
|
|
||||||
|
|
||||||
|
def test_cpu_model_with_amp():
|
||||||
|
"""
|
||||||
|
Make sure model trains on CPU
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
trainer_options = dict(
|
||||||
|
show_progress_bar=False,
|
||||||
|
logger=testing_utils.get_test_tube_logger(),
|
||||||
|
max_nb_epochs=1,
|
||||||
|
train_percent_check=0.4,
|
||||||
|
val_percent_check=0.4,
|
||||||
|
use_amp=True
|
||||||
|
)
|
||||||
|
|
||||||
|
model, hparams = testing_utils.get_model()
|
||||||
|
|
||||||
|
with pytest.raises((MisconfigurationException, ModuleNotFoundError)):
|
||||||
|
testing_utils.run_gpu_model_test(trainer_options, model, hparams, on_gpu=False)
|
||||||
|
|
||||||
|
|
||||||
|
def test_amp_gpu_dp():
|
||||||
|
"""
|
||||||
|
Make sure DP + AMP work
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
testing_utils.reset_seed()
|
||||||
|
|
||||||
|
if not testing_utils.can_run_gpu_test():
|
||||||
|
return
|
||||||
|
|
||||||
|
model, hparams = testing_utils.get_model()
|
||||||
|
trainer_options = dict(
|
||||||
|
max_nb_epochs=1,
|
||||||
|
gpus='0, 1', # test init with gpu string
|
||||||
|
distributed_backend='dp',
|
||||||
|
use_amp=True
|
||||||
|
)
|
||||||
|
with pytest.raises(MisconfigurationException):
|
||||||
|
testing_utils.run_gpu_model_test(trainer_options, model, hparams)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
pytest.main([__file__])
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import warnings
|
||||||
|
from argparse import Namespace
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from pl_examples import LightningTemplateModel
|
||||||
|
from pytorch_lightning import Trainer
|
||||||
|
from pytorch_lightning.callbacks import (
|
||||||
|
ModelCheckpoint,
|
||||||
|
)
|
||||||
|
from pytorch_lightning.logging import TestTubeLogger
|
||||||
|
from pytorch_lightning.testing import (
|
||||||
|
LightningTestModel,
|
||||||
|
)
|
||||||
|
|
||||||
|
# generate a list of random seeds for each test
|
||||||
|
RANDOM_FILE_PATHS = list(np.random.randint(12000, 19000, 1000))
|
||||||
|
RANDOM_PORTS = list(np.random.randint(12000, 19000, 1000))
|
||||||
|
ROOT_SEED = 1234
|
||||||
|
torch.manual_seed(ROOT_SEED)
|
||||||
|
np.random.seed(ROOT_SEED)
|
||||||
|
RANDOM_SEEDS = list(np.random.randint(0, 10000, 1000))
|
||||||
|
|
||||||
|
|
||||||
|
def run_model_test_no_loggers(trainer_options, model, hparams, on_gpu=True, min_acc=0.50):
|
||||||
|
save_dir = init_save_dir()
|
||||||
|
trainer_options['default_save_path'] = save_dir
|
||||||
|
|
||||||
|
# fit model
|
||||||
|
trainer = Trainer(**trainer_options)
|
||||||
|
result = trainer.fit(model)
|
||||||
|
|
||||||
|
# correct result and ok accuracy
|
||||||
|
assert result == 1, 'amp + ddp model failed to complete'
|
||||||
|
|
||||||
|
# test model loading
|
||||||
|
pretrained_model = load_model(trainer.logger.experiment,
|
||||||
|
trainer.checkpoint_callback.filepath)
|
||||||
|
|
||||||
|
# test new model accuracy
|
||||||
|
for dataloader in model.test_dataloader():
|
||||||
|
run_prediction(dataloader, pretrained_model, min_acc=min_acc)
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
clear_save_dir()
|
||||||
|
|
||||||
|
|
||||||
|
def run_gpu_model_test(trainer_options, model, hparams, on_gpu=True):
|
||||||
|
save_dir = init_save_dir()
|
||||||
|
|
||||||
|
# logger file to get meta
|
||||||
|
logger = get_test_tube_logger(False)
|
||||||
|
|
||||||
|
# logger file to get weights
|
||||||
|
checkpoint = init_checkpoint_callback(logger)
|
||||||
|
|
||||||
|
# add these to the trainer options
|
||||||
|
trainer_options['checkpoint_callback'] = checkpoint
|
||||||
|
trainer_options['logger'] = logger
|
||||||
|
|
||||||
|
# fit model
|
||||||
|
trainer = Trainer(**trainer_options)
|
||||||
|
result = trainer.fit(model)
|
||||||
|
|
||||||
|
# correct result and ok accuracy
|
||||||
|
assert result == 1, 'amp + ddp model failed to complete'
|
||||||
|
|
||||||
|
# test model loading
|
||||||
|
pretrained_model = load_model(logger.experiment, trainer.checkpoint_callback.filepath)
|
||||||
|
|
||||||
|
# test new model accuracy
|
||||||
|
[run_prediction(dataloader, pretrained_model) for dataloader in model.test_dataloader()]
|
||||||
|
|
||||||
|
if trainer.use_ddp or trainer.use_ddp2:
|
||||||
|
# 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(save_dir, logger)
|
||||||
|
trainer.hpc_load(save_dir, on_gpu=on_gpu)
|
||||||
|
|
||||||
|
clear_save_dir()
|
||||||
|
|
||||||
|
|
||||||
|
def get_hparams(continue_training=False, hpc_exp_number=0):
|
||||||
|
root_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
|
||||||
|
args = {
|
||||||
|
'drop_prob': 0.2,
|
||||||
|
'batch_size': 32,
|
||||||
|
'in_features': 28 * 28,
|
||||||
|
'learning_rate': 0.001 * 8,
|
||||||
|
'optimizer_name': 'adam',
|
||||||
|
'data_root': os.path.join(root_dir, 'mnist'),
|
||||||
|
'out_features': 10,
|
||||||
|
'hidden_dim': 1000}
|
||||||
|
|
||||||
|
if continue_training:
|
||||||
|
args['test_tube_do_checkpoint_load'] = True
|
||||||
|
args['hpc_exp_number'] = hpc_exp_number
|
||||||
|
|
||||||
|
hparams = Namespace(**args)
|
||||||
|
return hparams
|
||||||
|
|
||||||
|
|
||||||
|
def get_model(use_test_model=False, lbfgs=False):
|
||||||
|
# set up model with these hyperparams
|
||||||
|
hparams = get_hparams()
|
||||||
|
if lbfgs:
|
||||||
|
setattr(hparams, 'optimizer_name', 'lbfgs')
|
||||||
|
setattr(hparams, 'learning_rate', 0.002)
|
||||||
|
|
||||||
|
if use_test_model:
|
||||||
|
model = LightningTestModel(hparams)
|
||||||
|
else:
|
||||||
|
model = LightningTemplateModel(hparams)
|
||||||
|
|
||||||
|
return model, hparams
|
||||||
|
|
||||||
|
|
||||||
|
def get_test_tube_logger(debug=True, version=None):
|
||||||
|
# set up logger object without actually saving logs
|
||||||
|
root_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
save_dir = os.path.join(root_dir, 'save_dir')
|
||||||
|
logger = TestTubeLogger(save_dir, name='lightning_logs', debug=False, version=version)
|
||||||
|
return logger
|
||||||
|
|
||||||
|
|
||||||
|
def init_save_dir():
|
||||||
|
root_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
save_dir = os.path.join(root_dir, 'tests', 'save_dir')
|
||||||
|
|
||||||
|
if os.path.exists(save_dir):
|
||||||
|
n = RANDOM_FILE_PATHS.pop()
|
||||||
|
shutil.move(save_dir, save_dir + f'_{n}')
|
||||||
|
|
||||||
|
os.makedirs(save_dir, exist_ok=True)
|
||||||
|
|
||||||
|
return save_dir
|
||||||
|
|
||||||
|
|
||||||
|
def clear_save_dir():
|
||||||
|
root_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
save_dir = os.path.join(root_dir, 'save_dir')
|
||||||
|
if os.path.exists(save_dir):
|
||||||
|
n = RANDOM_FILE_PATHS.pop()
|
||||||
|
shutil.move(save_dir, save_dir + f'_{n}')
|
||||||
|
|
||||||
|
|
||||||
|
def load_model(exp, root_weights_dir, module_class=LightningTemplateModel):
|
||||||
|
# load trained model
|
||||||
|
tags_path = exp.get_data_path(exp.name, exp.version)
|
||||||
|
tags_path = os.path.join(tags_path, 'meta_tags.csv')
|
||||||
|
|
||||||
|
checkpoints = [x for x in os.listdir(root_weights_dir) if '.ckpt' in x]
|
||||||
|
weights_dir = os.path.join(root_weights_dir, checkpoints[0])
|
||||||
|
|
||||||
|
trained_model = module_class.load_from_metrics(weights_path=weights_dir,
|
||||||
|
tags_csv=tags_path)
|
||||||
|
|
||||||
|
assert trained_model is not None, 'loading model failed'
|
||||||
|
|
||||||
|
return trained_model
|
||||||
|
|
||||||
|
|
||||||
|
def run_prediction(dataloader, trained_model, dp=False, min_acc=0.50):
|
||||||
|
# run prediction on 1 batch
|
||||||
|
for batch in dataloader:
|
||||||
|
break
|
||||||
|
|
||||||
|
x, y = batch
|
||||||
|
x = x.view(x.size(0), -1)
|
||||||
|
|
||||||
|
if dp:
|
||||||
|
output = trained_model(batch, 0)
|
||||||
|
acc = output['val_acc']
|
||||||
|
acc = torch.mean(acc).item()
|
||||||
|
|
||||||
|
else:
|
||||||
|
y_hat = trained_model(x)
|
||||||
|
|
||||||
|
# acc
|
||||||
|
labels_hat = torch.argmax(y_hat, dim=1)
|
||||||
|
acc = torch.sum(y == labels_hat).item() / (len(y) * 1.0)
|
||||||
|
acc = torch.tensor(acc)
|
||||||
|
acc = acc.item()
|
||||||
|
|
||||||
|
assert acc > min_acc, f'this model is expected to get > {min_acc} in test set (it got {acc})'
|
||||||
|
|
||||||
|
|
||||||
|
def assert_ok_val_acc(trainer):
|
||||||
|
# this model should get 0.80+ acc
|
||||||
|
acc = trainer.training_tqdm_dict['val_acc']
|
||||||
|
assert acc > 0.50, f'model failed to get expected 0.50 validation accuracy. Got: {acc}'
|
||||||
|
|
||||||
|
|
||||||
|
def assert_ok_test_acc(trainer):
|
||||||
|
# this model should get 0.80+ acc
|
||||||
|
acc = trainer.training_tqdm_dict['test_acc']
|
||||||
|
assert acc > 0.50, f'model failed to get expected 0.50 validation accuracy. Got: {acc}'
|
||||||
|
|
||||||
|
|
||||||
|
def can_run_gpu_test():
|
||||||
|
if not torch.cuda.is_available():
|
||||||
|
warnings.warn('test_multi_gpu_model_ddp cannot run.'
|
||||||
|
' Rerun on a GPU node to run this test')
|
||||||
|
return False
|
||||||
|
if not torch.cuda.device_count() > 1:
|
||||||
|
warnings.warn('test_multi_gpu_model_ddp cannot run.'
|
||||||
|
' Rerun on a node with 2+ GPUs to run this test')
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def reset_seed():
|
||||||
|
SEED = RANDOM_SEEDS.pop()
|
||||||
|
torch.manual_seed(SEED)
|
||||||
|
np.random.seed(SEED)
|
||||||
|
|
||||||
|
|
||||||
|
def set_random_master_port():
|
||||||
|
port = RANDOM_PORTS.pop()
|
||||||
|
os.environ['MASTER_PORT'] = str(port)
|
||||||
|
|
||||||
|
|
||||||
|
def init_checkpoint_callback(logger):
|
||||||
|
exp = logger.experiment
|
||||||
|
exp_path = exp.get_data_path(exp.name, exp.version)
|
||||||
|
ckpt_dir = os.path.join(exp_path, 'checkpoints')
|
||||||
|
checkpoint = ModelCheckpoint(ckpt_dir)
|
||||||
|
return checkpoint
|
||||||
@@ -12,36 +12,40 @@
|
|||||||
# and also to help confirm pull requests to this project.
|
# and also to help confirm pull requests to this project.
|
||||||
|
|
||||||
[tox]
|
[tox]
|
||||||
envlist = py{35,36,37}
|
envlist = py{35,36,37,38}
|
||||||
|
|
||||||
[pytest]
|
# DROP, it is duplication of setup.cfg
|
||||||
log_cli = 0
|
# [pytest]
|
||||||
log_cli_level = CRITICAL
|
# log_cli = 0
|
||||||
log_cli_format = %(message)s
|
# log_cli_level = CRITICAL
|
||||||
log_file = pytest.log
|
# log_cli_format = %(message)s
|
||||||
log_file_level = DEBUG
|
# log_file = pytest.log
|
||||||
log_file_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)
|
# log_file_level = DEBUG
|
||||||
log_file_date_format=%Y-%m-%d %H:%M:%S
|
# log_file_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)
|
||||||
|
# log_file_date_format=%Y-%m-%d %H:%M:%S
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
basepython =
|
basepython =
|
||||||
py35: python3.5
|
py35: python3.5
|
||||||
py36: python3.6
|
py36: python3.6
|
||||||
py37: python3.7
|
py37: python3.7
|
||||||
|
py38: python3.8
|
||||||
deps =
|
deps =
|
||||||
-r requirements.txt
|
-r requirements.txt
|
||||||
-r ./tests/requirements.txt
|
-r ./tests/requirements.txt
|
||||||
commands =
|
commands =
|
||||||
|
pip list
|
||||||
check-manifest --ignore tox.ini
|
check-manifest --ignore tox.ini
|
||||||
python setup.py check -m -s
|
python setup.py check --metadata --strict
|
||||||
|
coverage run --source pytorch_lightning -m py.test pytorch_lightning tests pl_examples -v --doctest-modules
|
||||||
flake8 .
|
flake8 .
|
||||||
coverage run --source pytorch_lightning -m py.test pytorch_lightning tests examples -v --doctest-modules
|
|
||||||
|
|
||||||
[flake8]
|
# DROP, it is duplication of setup.cfg
|
||||||
exclude = .tox,*.egg,build,temp,examples/*
|
# [flake8]
|
||||||
select = E,W,F
|
# exclude = .tox,*.egg,build,temp,examples/*
|
||||||
doctests = True
|
# select = E,W,F
|
||||||
verbose = 2
|
# doctests = True
|
||||||
|
# verbose = 2
|
||||||
# https://pep8.readthedocs.io/en/latest/intro.html#error-codes
|
# https://pep8.readthedocs.io/en/latest/intro.html#error-codes
|
||||||
format = pylint
|
# format = pylint
|
||||||
max-line-length = 100
|
# max-line-length = 100
|
||||||
|
|||||||
Reference in New Issue
Block a user