Skip to content

Commit feb2785

Browse files
zou3519ezyang
authored andcommitted
Implement torch.util.bottleneck (#5216)
* Implement torch.util.bottleneck This is a tool that is intended to be used as initial exploratory debugging of bottlenecks in user scripts. Run it with python -m torch.utils.bottleneck /path/to/source/script.py * Refactor and address comments * Fix tests * Allow passing of args to the profiled script * Replace Variable
1 parent 3cc00e8 commit feb2785

File tree

8 files changed

+456
-0
lines changed

8 files changed

+456
-0
lines changed

docs/source/bottleneck.rst

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
torch.utils.bottleneck
2+
===============
3+
4+
.. currentmodule:: torch.utils.bottleneck
5+
6+
`torch.utils.bottleneck` is a tool that can be used as an initial step for
7+
debugging bottlenecks in your program. It summarizes runs of your script with
8+
the Python profiler and PyTorch's autograd profiler.
9+
10+
Run it on the command line with
11+
12+
::
13+
14+
python -m torch.utils.bottleneck -- /path/to/source/script.py [args]
15+
16+
where [args] are any number of arguments to `script.py`, or run
17+
``python -m torch.utils.bottleneck -h`` for more usage instructions.
18+
19+
.. warning::
20+
Because your script will be profiled, please ensure that it exits in a
21+
finite amount of time.
22+
23+
.. warning::
24+
Due to the asynchronous nature of CUDA kernels, when running against
25+
CUDA code, the cProfile output and CPU-mode autograd profilers may
26+
not show correct timings. In this case, the CUDA-mode autograd
27+
profiler is better at assigning blame to the relevant operator(s).
28+
29+
For more complicated uses of the profilers (like in a multi-GPU case),
30+
please see https://docs.python.org/3/library/profile.html
31+
or :func:`torch.autograd.profiler.profile()` for more information.

docs/source/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ PyTorch is an optimized tensor library for deep learning using GPUs and CPUs.
3939
data
4040
model_zoo
4141
onnx
42+
bottleneck
4243

4344
.. toctree::
4445
:glob:

test/bottleneck/test.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import torch
2+
3+
x = torch.ones((3, 3), requires_grad=True)
4+
(3 * x).sum().backward()

test/bottleneck/test_args.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import argparse
2+
import torch
3+
4+
if __name__ == '__main__':
5+
parser = argparse.ArgumentParser()
6+
7+
# Required args. Raises error if they aren't passed.
8+
parser.add_argument('--foo', help='foo', required=True)
9+
parser.add_argument('--bar', help='bar', required=True)
10+
_ = parser.parse_args()
11+
12+
x = torch.ones((3, 3), requires_grad=True)
13+
(3 * x).sum().backward()

test/bottleneck/test_cuda.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import torch
2+
import torch.nn as nn
3+
4+
5+
class Model(nn.Module):
6+
def __init__(self):
7+
super(Model, self).__init__()
8+
self.linear = nn.Linear(20, 20)
9+
10+
def forward(self, input):
11+
out = self.linear(input[:, 10:30])
12+
return out.sum()
13+
14+
15+
def main():
16+
data = torch.randn(10, 50).cuda()
17+
model = Model().cuda()
18+
optimizer = torch.optim.SGD(model.parameters(), lr=0.0001)
19+
for i in range(10):
20+
optimizer.zero_grad()
21+
loss = model(data)
22+
loss.backward()
23+
optimizer.step()
24+
25+
26+
if __name__ == '__main__':
27+
main()

test/test_utils.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import print_function
22
import sys
33
import os
4+
import re
45
import math
56
import shutil
67
import random
@@ -385,6 +386,105 @@ def _transform_MultiMarginCriterion(self, input, target):
385386
return input, target.sub(1)
386387

387388

389+
class TestBottleneck(TestCase):
390+
def _run(self, command):
391+
"""Returns (return-code, stdout, stderr)"""
392+
import subprocess
393+
from common import PY3
394+
395+
p = subprocess.Popen(command, stdout=subprocess.PIPE,
396+
stderr=subprocess.PIPE, shell=True)
397+
output, err = p.communicate()
398+
rc = p.returncode
399+
if PY3:
400+
output = output.decode("ascii")
401+
err = err.decode("ascii")
402+
return (rc, output, err)
403+
404+
def _run_bottleneck(self, test_file, scriptargs=''):
405+
import os
406+
curdir = os.path.dirname(os.path.abspath(__file__))
407+
filepath = '{}/{}'.format(curdir, test_file)
408+
if scriptargs != '':
409+
mark = '-- '
410+
scriptargs = ' {}'.format(scriptargs)
411+
else:
412+
mark = ''
413+
rc, out, err = self._run(
414+
'python -m torch.utils.bottleneck {}{}{}'.format(mark, filepath, scriptargs))
415+
return rc, out, err
416+
417+
def _check_run_args(self):
418+
# Check that this fails due to missing args
419+
rc, out, err = self._run_bottleneck('bottleneck/test_args.py')
420+
self.assertEqual(rc, 2, None, self._fail_msg('Missing args should error', out + err))
421+
422+
# This should succeed
423+
rc, out, err = self._run_bottleneck('bottleneck/test_args.py', '--foo foo --bar bar')
424+
self.assertEqual(rc, 0, None, self._fail_msg('Should pass args to script', out + err))
425+
426+
def _fail_msg(self, msg, output):
427+
return '{}, output was:\n{}'.format(msg, output)
428+
429+
def _check_environment_summary(self, output):
430+
results = re.search('Environment Summary', output)
431+
self.assertIsNotNone(results, self._fail_msg('Should have Enviroment Summary', output))
432+
433+
# Up to five lines away from the heading, there should be the version number
434+
results = re.search(r'Environment Summary.*(\n.*){,5}\nPyTorch \d+\.\d+', output)
435+
self.assertIsNotNone(results, self._fail_msg('Should have PyTorch version', output))
436+
437+
def _check_cprof_summary(self, output):
438+
results = re.search('cProfile output', output)
439+
self.assertIsNotNone(results, self._fail_msg('Should have cProfile output', output))
440+
441+
# This assumes that after the cProfile output section we have
442+
# the autograd profiler output
443+
results = re.search(r'cProfile output.*(\n.*){6,50}\n.*autograd profiler output', output)
444+
self.assertIsNotNone(results, self._fail_msg(
445+
'Distance between cProfile and autograd prof out not in [6, 50] lines', output))
446+
447+
def _check_autograd_summary(self, output):
448+
results = re.search('autograd profiler output', output)
449+
self.assertIsNotNone(results, self._fail_msg('Should have autograd profiler output', output))
450+
451+
# This assumes that after the autograd profiler output is the end of the
452+
# output.
453+
results = re.search(r'autograd profiler output.*(\n.*){6,100}', output)
454+
self.assertIsNotNone(results, self._fail_msg(
455+
'Distance between autograd prof output and end of output not in [6, 100] lines', output))
456+
457+
def _check_cuda(self, output):
458+
if torch.cuda.is_available():
459+
results = re.search('CUDA mode', output)
460+
self.assertIsNotNone(results, self._fail_msg('Should tell users CUDA', output))
461+
else:
462+
results = re.search('CUDA mode', output)
463+
self.assertIsNone(results, self._fail_msg('Should not tell users about CUDA', output))
464+
465+
@unittest.skipIf(torch.cuda.is_available(), 'CPU-only test')
466+
def test_cpu_only(self):
467+
rc, out, err = self._run_bottleneck('bottleneck/test.py')
468+
self.assertEqual(rc, 0, 'Run failed with\n{}'.format(err))
469+
470+
self._check_run_args()
471+
self._check_environment_summary(out)
472+
self._check_autograd_summary(out)
473+
self._check_cprof_summary(out)
474+
self._check_cuda(out)
475+
476+
@unittest.skipIf(not torch.cuda.is_available(), 'No CUDA')
477+
def test_cuda(self):
478+
rc, out, err = self._run_bottleneck('bottleneck/test_cuda.py')
479+
self.assertEqual(rc, 0, 'Run failed with\n{}'.format(err))
480+
481+
self._check_run_args()
482+
self._check_environment_summary(out)
483+
self._check_autograd_summary(out)
484+
self._check_cprof_summary(out)
485+
self._check_cuda(out)
486+
487+
388488
class TestONNXUtils(TestCase):
389489
def test_prepare_onnx_paddings(self):
390490
sizes = [2, 3, 4]

torch/utils/bottleneck/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)