As title. The hydra-joblib-launcher fails when setting num_workers>0 in PyTorch's DataLoader. If num_workers==0 everything works just fine. However, the speedup achieved with joblib multiprocessing barely (if at all) compensates for increased runtime of individual jobs.
```python title="MNIST_hydra.py"
import os
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader
from torchvision.datasets import MNIST
from torchvision import transforms
import hydra
class Model(nn.Module):
def __init__(self):
super().__init__()
self.l1 = torch.nn.Linear(28 * 28, 10)
def forward(self, x):
return torch.relu(self.l1(x.view(x.size(0), -1)))
@hydra.main()
def main(cfg):
data_set = MNIST(os.getcwd(), download=True, train=True, transform=transforms.ToTensor())
data_loader = DataLoader(data_set, batch_size=64, num_workers=cfg.num_workers)
model = Model().cuda()
optim = torch.optim.Adam(model.parameters(), lr=cfg.lr)
for batch in data_loader:
x, y = batch
y_hat = model(x.cuda())
loss = F.cross_entropy(y_hat, y.cuda())
print(f'completed {cfg.lr}')
if __name__ == '__main__':
main()
This works:
```commandline
python MNIST_hydra.py +num_workers=0 +lr=1e-4,1e-3,1e-2 hydra/launcher=joblib -m
This fails:
python MNIST_hydra.py +num_workers=1 +lr=1e-4,1e-3,1e-2 hydra/launcher=joblib -m
with error: (note that there are several times more cores available than required for this config.)
* Stack trace/error message *
/usr/lib/python3.7/multiprocessing/semaphore_tracker.py:144: UserWarning: semaphore_tracker: There appear to be 1 leaked semaphores to clean up at shutdown
len(cache))
/usr/lib/python3.7/multiprocessing/semaphore_tracker.py:144: UserWarning: semaphore_tracker: There appear to be 1 leaked semaphores to clean up at shutdown
len(cache))
/usr/lib/python3.7/multiprocessing/semaphore_tracker.py:144: UserWarning: semaphore_tracker: There appear to be 1 leaked semaphores to clean up at shutdown
len(cache))
joblib.externals.loky.process_executor._RemoteTraceback:
"""
Traceback (most recent call last):
File "/opt/.virtualenvs/l2r/lib/python3.7/site-packages/joblib/externals/loky/process_executor.py", line 431, in _process_worker
r = call_item()
File "/opt/.virtualenvs/l2r/lib/python3.7/site-packages/joblib/externals/loky/process_executor.py", line 285, in __call__
return self.fn(*self.args, **self.kwargs)
File "/opt/.virtualenvs/l2r/lib/python3.7/site-packages/joblib/_parallel_backends.py", line 595, in __call__
return self.func(*args, **kwargs)
File "/opt/.virtualenvs/l2r/lib/python3.7/site-packages/joblib/parallel.py", line 253, in __call__
for func, args, kwargs in self.items]
File "/opt/.virtualenvs/l2r/lib/python3.7/site-packages/joblib/parallel.py", line 253, in <listcomp>
for func, args, kwargs in self.items]
File "/opt/.virtualenvs/l2r/lib/python3.7/site-packages/hydra_plugins/hydra_joblib_launcher/_core.py", line 47, in execute_job
job_subdir_key="hydra.sweep.subdir",
File "/opt/.virtualenvs/l2r/lib/python3.7/site-packages/hydra/core/utils.py", line 125, in run_job
ret.return_value = task_function(task_cfg)
File "MNIST_hydra.py", line 28, in main
for batch in data_loader:
File "/opt/.virtualenvs/l2r/lib/python3.7/site-packages/torch/utils/data/dataloader.py", line 291, in __iter__
return _MultiProcessingDataLoaderIter(self)
File "/opt/.virtualenvs/l2r/lib/python3.7/site-packages/torch/utils/data/dataloader.py", line 737, in __init__
w.start()
File "/usr/lib/python3.7/multiprocessing/process.py", line 112, in start
self._popen = self._Popen(self)
File "/usr/lib/python3.7/multiprocessing/context.py", line 223, in _Popen
return _default_context.get_context().Process._Popen(process_obj)
File "/opt/.virtualenvs/l2r/lib/python3.7/site-packages/joblib/externals/loky/backend/process.py", line 39, in _Popen
return Popen(process_obj)
File "/opt/.virtualenvs/l2r/lib/python3.7/site-packages/joblib/externals/loky/backend/popen_loky_posix.py", line 52, in __init__
self._launch(process_obj)
File "/opt/.virtualenvs/l2r/lib/python3.7/site-packages/joblib/externals/loky/backend/popen_loky_posix.py", line 157, in _launch
pid = fork_exec(cmd_python, self._fds, env=process_obj.env)
AttributeError: 'Process' object has no attribute 'env'
"""
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/opt/.virtualenvs/l2r/lib/python3.7/site-packages/hydra/_internal/utils.py", line 207, in run_and_report
return func()
File "/opt/.virtualenvs/l2r/lib/python3.7/site-packages/hydra/_internal/utils.py", line 367, in <lambda>
overrides=args.overrides,
File "/opt/.virtualenvs/l2r/lib/python3.7/site-packages/hydra/_internal/hydra.py", line 136, in multirun
return sweeper.sweep(arguments=task_overrides)
File "/opt/.virtualenvs/l2r/lib/python3.7/site-packages/hydra/_internal/core_plugins/basic_sweeper.py", line 154, in sweep
results = self.launcher.launch(batch, initial_job_idx=initial_job_idx)
File "/opt/.virtualenvs/l2r/lib/python3.7/site-packages/hydra_plugins/hydra_joblib_launcher/joblib_launcher.py", line 46, in launch
launcher=self, job_overrides=job_overrides, initial_job_idx=initial_job_idx
File "/opt/.virtualenvs/l2r/lib/python3.7/site-packages/hydra_plugins/hydra_joblib_launcher/_core.py", line 98, in launch
for idx, overrides in enumerate(job_overrides)
File "/opt/.virtualenvs/l2r/lib/python3.7/site-packages/joblib/parallel.py", line 1042, in __call__
self.retrieve()
File "/opt/.virtualenvs/l2r/lib/python3.7/site-packages/joblib/parallel.py", line 921, in retrieve
self._output.extend(job.get(timeout=self.timeout))
File "/opt/.virtualenvs/l2r/lib/python3.7/site-packages/joblib/_parallel_backends.py", line 542, in wrap_future_result
return future.result(timeout=timeout)
File "/usr/lib/python3.7/concurrent/futures/_base.py", line 435, in result
return self.__get_result()
File "/usr/lib/python3.7/concurrent/futures/_base.py", line 384, in __get_result
raise self._exception
AttributeError: 'Process' object has no attribute 'env'
/opt/.virtualenvs/l2r/lib/python3.7/site-packages/joblib/externals/loky/backend/resource_tracker.py:320: UserWarning: resource_tracker: There appear to be 22 leaked semlock objects to clean up at shutdown
(len(rtype_registry), rtype))
Being able to use hydra-joblib-launcher and num_workers>0 in PyTorch at the same time.
Add any other context about the problem here.
This is likely an issue between joblib and PyTorch.
Try to reproduce with joblib and PyTorch. If you can, please file an issue against joblib and / or PyTorch.
In the mean time, you might want to try out the submitit plugin, which also has a local mode (similar to joblib). Maybe it will work better.
@jan-matthis, fyi.
I get the same error using PyTorch/joblib, so not hydra related indeed. Will give the submitit plugin a go later and see if that works. Will report back after as a reference to others before closing the issue.
Thanks for checking.
It would be great if you can file an issue against joblib to see what they think (please tag me if you do).
@omry joblib issue at https://github.com/joblib/joblib/issues/1104
Submitit seems to be a workaround, but I can't get submitit_local to work properly. With joblib I can fix the number of jobs running simultaneously to say 3 by setting n_jobs=3. I expected to be able to do the same thing with submitit using the following config:
# @package hydra.launcher
_target_: hydra_plugins.hydra_submitit_launcher.submitit_launcher.LocalLauncher
tasks_per_node: 3
nodes: 1
However, it seems that this just creates n==3 jobs on the same pid but still runs the entire sweep at once. This obviously fails due to resource constraints.
Click for output tree created using submitit
The sweep contains 19 configurations and for each of them there are 3 jobs created. See below where
$ tree
.
โโโ 0
โย ย โโโ events.out.tfevents.1600152210.gerard.14558.0
โย ย โโโ events.out.tfevents.1600152211.gerard.14559.0
โย ย โโโ events.out.tfevents.1600152212.gerard.14557.0
โย ย โโโ my_app.log
โโโ 1
โย ย โโโ events.out.tfevents.1600152212.gerard.14561.0
โย ย โโโ events.out.tfevents.1600152213.gerard.14563.0
โย ย โโโ events.out.tfevents.1600152214.gerard.14562.0
โย ย โโโ my_app.log
โโโ 10
โย ย โโโ checkpoints
โย ย โโโ events.out.tfevents.1600152222.gerard.14805.0
โย ย โโโ events.out.tfevents.1600152223.gerard.14799.0
โย ย โโโ events.out.tfevents.1600152223.gerard.14802.0
โย ย โโโ hparams.yaml
โย ย โโโ my_app.log
โโโ 11
โย ย โโโ events.out.tfevents.1600152223.gerard.14853.0
โย ย โโโ events.out.tfevents.1600152223.gerard.14854.0
โย ย โโโ events.out.tfevents.1600152224.gerard.14852.0
โย ย โโโ my_app.log
โโโ 12
โย ย โโโ checkpoints
โย ย โโโ events.out.tfevents.1600152224.gerard.14898.0
โย ย โโโ events.out.tfevents.1600152224.gerard.14899.0
โย ย โโโ events.out.tfevents.1600152224.gerard.14901.0
โย ย โโโ hparams.yaml
โย ย โโโ my_app.log
โโโ 13
โย ย โโโ events.out.tfevents.1600152224.gerard.14918.0
โย ย โโโ events.out.tfevents.1600152224.gerard.14919.0
โย ย โโโ events.out.tfevents.1600152224.gerard.14920.0
โย ย โโโ my_app.log
โโโ 14
โย ย โโโ checkpoints
โย ย โย ย โโโ epoch=29.ckpt
โย ย โย ย โโโ epoch=34.ckpt
โย ย โย ย โโโ epoch=39.ckpt
โย ย โย ย โโโ epoch=44.ckpt
โย ย โย ย โโโ epoch=49.ckpt
โย ย โโโ events.out.tfevents.1600152224.gerard.14989.0
โย ย โโโ events.out.tfevents.1600152224.gerard.14991.0
โย ย โโโ events.out.tfevents.1600152225.gerard.14990.0
โย ย โโโ hparams.yaml
โย ย โโโ my_app.log
โโโ 15
โย ย โโโ checkpoints
โย ย โย ย โโโ epoch=29.ckpt
โย ย โย ย โโโ epoch=34.ckpt
โย ย โย ย โโโ epoch=39.ckpt
โย ย โย ย โโโ epoch=44.ckpt
โย ย โย ย โโโ epoch=49.ckpt
โย ย โโโ events.out.tfevents.1600152224.gerard.15036.0
โย ย โโโ events.out.tfevents.1600152225.gerard.15038.0
โย ย โโโ events.out.tfevents.1600152226.gerard.15037.0
โย ย โโโ hparams.yaml
โย ย โโโ my_app.log
โโโ 16
โย ย โโโ checkpoints
โย ย โโโ events.out.tfevents.1600152225.gerard.15096.0
โย ย โโโ events.out.tfevents.1600152226.gerard.15094.0
โย ย โโโ events.out.tfevents.1600152226.gerard.15095.0
โย ย โโโ hparams.yaml
โย ย โโโ my_app.log
โโโ 17
โย ย โโโ checkpoints
โย ย โย ย โโโ epoch=29.ckpt
โย ย โย ย โโโ epoch=34.ckpt
โย ย โย ย โโโ epoch=39.ckpt
โย ย โย ย โโโ epoch=44.ckpt
โย ย โย ย โโโ epoch=49.ckpt
โย ย โโโ events.out.tfevents.1600152225.gerard.15122.0
โย ย โโโ events.out.tfevents.1600152226.gerard.15121.0
โย ย โโโ events.out.tfevents.1600152226.gerard.15123.0
โย ย โโโ hparams.yaml
โย ย โโโ my_app.log
โโโ 18
โย ย โโโ checkpoints
โย ย โโโ events.out.tfevents.1600152226.gerard.15178.0
โย ย โโโ events.out.tfevents.1600152226.gerard.15179.0
โย ย โโโ events.out.tfevents.1600152226.gerard.15180.0
โย ย โโโ hparams.yaml
โย ย โโโ my_app.log
โโโ 2
โย ย โโโ events.out.tfevents.1600152216.gerard.14582.0
โย ย โโโ events.out.tfevents.1600152216.gerard.14605.0
โย ย โโโ events.out.tfevents.1600152216.gerard.14606.0
โย ย โโโ my_app.log
โโโ 3
โย ย โโโ checkpoints
โย ย โโโ events.out.tfevents.1600152217.gerard.14624.0
โย ย โโโ events.out.tfevents.1600152217.gerard.14626.0
โย ย โโโ events.out.tfevents.1600152218.gerard.14623.0
โย ย โโโ hparams.yaml
โย ย โโโ my_app.log
โโโ 4
โย ย โโโ checkpoints
โย ย โย ย โโโ epoch=29.ckpt
โย ย โย ย โโโ epoch=34.ckpt
โย ย โย ย โโโ epoch=39.ckpt
โย ย โย ย โโโ epoch=44.ckpt
โย ย โย ย โโโ epoch=49.ckpt
โย ย โโโ events.out.tfevents.1600152219.gerard.14679.0
โย ย โโโ events.out.tfevents.1600152219.gerard.14681.0
โย ย โโโ events.out.tfevents.1600152219.gerard.14682.0
โย ย โโโ hparams.yaml
โย ย โโโ my_app.log
โโโ 5
โย ย โโโ events.out.tfevents.1600152219.gerard.14685.0
โย ย โโโ events.out.tfevents.1600152219.gerard.14697.0
โย ย โโโ events.out.tfevents.1600152219.gerard.14699.0
โย ย โโโ my_app.log
โโโ 6
โย ย โโโ events.out.tfevents.1600152220.gerard.14727.0
โย ย โโโ events.out.tfevents.1600152221.gerard.14725.0
โย ย โโโ events.out.tfevents.1600152221.gerard.14726.0
โย ย โโโ my_app.log
โโโ 7
โย ย โโโ checkpoints
โย ย โโโ events.out.tfevents.1600152220.gerard.14744.0
โย ย โโโ events.out.tfevents.1600152221.gerard.14743.0
โย ย โโโ events.out.tfevents.1600152222.gerard.14742.0
โย ย โโโ hparams.yaml
โย ย โโโ my_app.log
โโโ 8
โย ย โโโ events.out.tfevents.1600152221.gerard.14751.0
โย ย โโโ events.out.tfevents.1600152221.gerard.14754.0
โย ย โโโ events.out.tfevents.1600152222.gerard.14753.0
โย ย โโโ my_app.log
โโโ 9
โย ย โโโ events.out.tfevents.1600152221.gerard.14783.0
โย ย โโโ events.out.tfevents.1600152223.gerard.14785.0
โย ย โโโ events.out.tfevents.1600152224.gerard.14788.0
โย ย โโโ my_app.log
โโโ multirun.yaml
โโโ .submitit
โโโ 14556
โย ย โโโ 14556_0_log.err
โย ย โโโ 14556_0_log.out
โย ย โโโ 14556_1_log.err
โย ย โโโ 14556_1_log.out
โย ย โโโ 14556_2_log.err
โย ย โโโ 14556_2_log.out
โย ย โโโ 14556_submitted.pkl
โโโ 14932
โย ย โโโ 14932_0_log.err
โย ย โโโ 14932_0_log.out
โย ย โโโ 14932_0_result.pkl
โย ย โโโ 14932_1_log.err
โย ย โโโ 14932_1_log.out
โย ย โโโ 14932_2_log.err
โย ย โโโ 14932_2_log.out
โย ย โโโ 14932_submitted.pkl
โโโ 14976
โย ย โโโ 14976_0_log.err
โย ย โโโ 14976_0_log.out
โย ย โโโ 14976_1_log.err
โย ย โโโ 14976_1_log.out
โย ย โโโ 14976_2_log.err
โย ย โโโ 14976_2_log.out
โย ย โโโ 14976_2_result.pkl
โย ย โโโ 14976_submitted.pkl
โโโ 15030
โย ย โโโ 15030_0_log.err
โย ย โโโ 15030_0_log.out
โย ย โโโ 15030_0_result.pkl
โย ย โโโ 15030_1_log.err
โย ย โโโ 15030_1_log.out
โย ย โโโ 15030_2_log.err
โย ย โโโ 15030_2_log.out
โย ย โโโ 15030_submitted.pkl
I omitted the .hydra folders and many results in .submitit for brevity if you can call it that.
Am I doing something wrong or is this the intended behavior?
Also, is it possible to suppress generation of the .submitit folder? Hydra already logs everything that I am interested in. Training ML models using progress trackers such as tqdm also creates massive log files here.
Generally submitit-local is mostly used for debugging, I am not sure it has this functionality.
@jrapin ?
About the .submitit directory:
It's not possible to disable it. Especially when running with SLURM (which is what submitit it really for) - You can have failures to start the job before the process even get to run. Those are impossible to debug without the .submitit directory.
@gerardsn, ~I think the repro you created in the joblib is different.~
~In the joblib launcher, the launcher spawn your processes before your @hydra.main() function is called, only then the workers are starting.~
~In the your pure joblib repro you first spwan the dataloader workers and then the joblib workers.~
Never mind, your repro is fine.
Looking at the stacktrace carefully, you can see that somehow torch dataloader spawning processes is hitting the joblib code.
They must somehow be manipulating the process spawning API pytorch is using to go through joblib and they are failing there.
File "MNIST_hydra.py", line 25, in main
for batch in data_loader:
File "/home/omry/miniconda3/envs/torch-config/lib/python3.8/site-packages/torch/utils/data/dataloader.py", line 291, in __iter__
return _MultiProcessingDataLoaderIter(self)
File "/home/omry/miniconda3/envs/torch-config/lib/python3.8/site-packages/torch/utils/data/dataloader.py", line 737, in __init__
w.start()
File "/home/omry/miniconda3/envs/torch-config/lib/python3.8/multiprocessing/process.py", line 121, in start
self._popen = self._Popen(self)
File "/home/omry/miniconda3/envs/torch-config/lib/python3.8/multiprocessing/context.py", line 224, in _Popen
return _default_context.get_context().Process._Popen(process_obj)
File "/home/omry/miniconda3/envs/torch-config/lib/python3.8/site-packages/joblib/externals/loky/backend/process.py", line 39, in _Popen
return Popen(process_obj)
File "/home/omry/miniconda3/envs/torch-config/lib/python3.8/site-packages/joblib/externals/loky/backend/popen_loky_posix.py", line 52, in __init__
self._launch(process_obj)
File "/home/omry/miniconda3/envs/torch-config/lib/python3.8/site-packages/joblib/externals/loky/backend/popen_loky_posix.py", line 157, in _launch
pid = fork_exec(cmd_python, self._fds, env=process_obj.env)
AttributeError: 'Process' object has no attribute 'env'
Changing Joblib to use prefer=threads instead of backend=loky runs without failure (no errors, not sure if it does the right thing). See change in last line of code below
import os, sys
import torch, torch.nn as nn, torch.nn.functional as F
from torch.utils.data import DataLoader
from torchvision import transforms, datasets
from joblib import Parallel, delayed
class Model(nn.Module):
def __init__(self):
super().__init__()
self.l1 = torch.nn.Linear(28 * 28, 10)
def forward(self, x):
return torch.relu(self.l1(x.view(x.size(0), -1)))
def main(num_workers, lr):
data_set = datasets.MNIST(os.getcwd(), download=True, train=True, transform=transforms.ToTensor())
data_loader = DataLoader(data_set, batch_size=64, num_workers=num_workers)
model = Model()
optim = torch.optim.Adam(model.parameters(), lr=lr)
for batch in data_loader:
x, y = batch
y_hat = model(x)
loss = F.cross_entropy(y_hat, y)
print(f'completed {lr}')
if __name__ == '__main__':
num_workers = int(sys.argv[1])
# Parallel(n_jobs=3, backend='loky')(delayed(main)(num_workers, lr) for lr in [1e-4, 1e-3, 1e-2])
Parallel(n_jobs=3, prefer='threads')(delayed(main)(num_workers, lr) for lr in [1e-4, 1e-3, 1e-2])
Unfortunately hydra appears to be incompatible with anything else than the loky backend #423. Commenting out the hardcoded loky backend in the hydra-joblib-launcher does run, but it does not produce the expected results and does not run parallel jobs.
Hydra is not compatible with the threaded backend. You are going to have a bad time.
Closing as this is not a bug in Hydra. Feel free to continue discussing here.
Alright. I finally managed to get it to work by forcing the PyTorch DataLoader to use multiprocessing_context='fork'. Apparently the loky backend in joblib clashes with the other forking methods. Below is minimum working example using hydra, hydra-joblib-launcher, and DataLoader(..., num_workers>0, multiprocessing_context='fork').
import os
import torch, torch.nn as nn, torch.nn.functional as F
from torch.utils.data import DataLoader
from torchvision import transforms, datasets
import hydra
class Model(nn.Module):
def __init__(self):
super().__init__()
self.l1 = torch.nn.Linear(28 * 28, 10)
def forward(self, x):
return torch.relu(self.l1(x.view(x.size(0), -1)))
@hydra.main()
def main(cfg):
data_set = datasets.MNIST(os.getcwd(), download=True, train=True, transform=transforms.ToTensor())
data_loader = DataLoader(data_set, batch_size=64, num_workers=cfg.num_workers, multiprocessing_context='fork')
model = Model()
optim = torch.optim.Adam(model.parameters(), lr=cfg.lr)
for batch in data_loader:
x, y = batch
y_hat = model(x)
loss = F.cross_entropy(y_hat, y)
print(f'completed {cfg.lr}')
if __name__ == '__main__':
main()
and
python MNIST_hydra.py hydra/launcher=joblib +lr=1e-4,1e-3,1e-2 +num_workers=1 -m
It is worth noting that if one of the jobs in the sweep fails, the entire sweep will terminate once the currently running jobs are finished.
Awesome, thanks.
Generally submitit-local is mostly used for debugging, I am not sure it has this functionality.
@jrapin ?
Hey, sorry for jumping in late (busy full time on a deadline :s)
yes submitit-local is mostly for debugging and takes some liberties with resources, but here that's not exactly the problem. ntasks_per_node is rather low level and reproduces the behavior or slurm, which is indeed running the same code each time. It's up to the code to actually split the work, based on environment variables (which are accessible through submitit.JobEnvironment().global_id for instance)
Most helpful comment
Alright. I finally managed to get it to work by forcing the PyTorch
DataLoaderto usemultiprocessing_context='fork'. Apparently the loky backend in joblib clashes with the other forking methods. Below is minimum working example usinghydra,hydra-joblib-launcher, andDataLoader(..., num_workers>0, multiprocessing_context='fork').and
It is worth noting that if one of the jobs in the sweep fails, the entire sweep will terminate once the currently running jobs are finished.