Skip to content

Commit 6b2e648

Browse files
bedapislAdam Blažek
authored andcommitted
Profiling of neural networks (#26)
* Add profiling of neural networks * Tests * Refactor profiling * Cosmetic changes * Tests * Cosmetic changes * Cosmetic changes * Refactoring profiler * Documentation * Add profiling test
1 parent e88f5ec commit 6b2e648

File tree

7 files changed

+122
-13
lines changed

7 files changed

+122
-13
lines changed

docs/advanced.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
Profiling networks
2+
------------------
3+
Profiling execution of tensorflow graph can be enabled with following setting:
4+
5+
.. code-block:: yaml
6+
:caption config.yaml
7+
8+
model:
9+
profile: True
10+
keep_profiles: 10
11+
12+
This saves profiles of last 10 runs to the log directory (output directory).
13+
Profiles are in JSON format and can be viewed using Google Chrome.
14+
To view them go to address `chrome://tracing/` and load the json file.
15+
16+
Gradient clipping
17+
-----------------
18+
For gradient clipping use following setting:
19+
20+
.. code-block:: yaml
21+
:caption config.yaml
22+
23+
model:
24+
clip_gradient: 5.0
25+
26+
This clips the absolute value of gradient to 5.0.
27+
Note that the clipping is done to raw gradients before they are multiplied by learning rate or processed in other ways.

docs/conf.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# General information about the project.
1414
project = 'emloop-tensorflow'
1515
copyright = '2018, Iterait a.s.'
16-
author = 'Blazek Adam, Belohlavek Petr, Matzner Filip'
16+
author = 'Blazek Adam, Belohlavek Petr, Matzner Filip, Bedrich Pisl'
1717

1818
# The short X.Y version.
1919
version = '.'.join(pkg_resources.get_distribution("emloop-tensorflow").version.split('.')[:2])
@@ -37,6 +37,7 @@
3737
("Tutorial", "tutorial"),
3838
("Model Regularization", "regularization"),
3939
("Multi GPU models", "multi_gpu"),
40+
("Advanced", "advanced"),
4041
("API Reference", "emloop_tensorflow/index"),
4142
],
4243
})

emloop_tensorflow/frozen_model.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from .graph_tower import GraphTower
1010
from .model import BaseModel
11+
from .utils import Profiler
1112

1213

1314
class FrozenModel(el.AbstractModel):
@@ -28,17 +29,19 @@ class FrozenModel(el.AbstractModel):
2829
2930
"""
3031

31-
def __init__(self,
32-
inputs: List[str], outputs: List[str], restore_from: str,
33-
session_config: Optional[dict]=None, n_gpus: int=0, **_):
32+
def __init__(self, inputs: List[str], outputs: List[str], restore_from: str, log_dir: Optional[str]=None,
33+
session_config: Optional[dict]=None, n_gpus: int=0, profile: bool=False, keep_profiles: int=5, **_):
3434
"""
3535
Initialize new :py:class:`FrozenModel` instance.
3636
37+
:param log_dir: output directory
3738
:param inputs: model input names
3839
:param outputs: model output names
3940
:param restore_from: restore model path (either a dir or a .pb file)
4041
:param session_config: TF session configuration dict
4142
:param n_gpus: number of GPUs to use (either 0 or 1)
43+
:param profile: if true, profile the speed of model inference and save profiles to the specified log_dir
44+
:param keep_profiles: how many profiles are saved
4245
"""
4346
super().__init__(None, '', restore_from)
4447
assert 0 <= n_gpus <= 1, 'FrozenModel can be used only with n_gpus=0 or n_gpus=1'
@@ -60,6 +63,13 @@ def __init__(self,
6063
except KeyError:
6164
self._is_training = tf.placeholder(tf.bool, [], BaseModel.TRAINING_FLAG_NAME)
6265

66+
if profile and not log_dir:
67+
raise ValueError('log_dir has to be specified with profile set to True')
68+
69+
self._profile = profile
70+
if profile:
71+
self._profiler = Profiler(log_dir, keep_profiles, self._session)
72+
6373
def run(self, batch: el.Batch, train: bool=False, stream: el.datasets.StreamWrapper=None) -> Mapping[str, object]:
6474
"""
6575
Run the model with the given ``batch``.
@@ -83,7 +93,10 @@ def run(self, batch: el.Batch, train: bool=False, stream: el.datasets.StreamWrap
8393
for output_name in self.output_names:
8494
fetches.append(self._tower[output_name])
8595

86-
outputs = self._session.run(fetches=fetches, feed_dict=feed_dict)
96+
if self._profile:
97+
outputs = self._profiler.run(fetches=fetches, feed_dict=feed_dict)
98+
else:
99+
outputs = self._session.run(fetches=fetches, feed_dict=feed_dict)
87100

88101
return dict(zip(self.output_names, outputs))
89102

emloop_tensorflow/model.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import logging
33
from os import path
44
from abc import ABCMeta
5-
from typing import List, Mapping, Optional
5+
from typing import List, Mapping, Optional, Dict
66
from glob import glob
77

88
import numpy as np
@@ -11,7 +11,7 @@
1111

1212
from .third_party.tensorflow.freeze_graph import freeze_graph
1313
from .third_party.tensorflow.average_gradients import average_gradients
14-
from .utils import create_optimizer
14+
from .utils import create_optimizer, Profiler
1515
from .graph_tower import GraphTower
1616

1717
DEFAULT_LOSS_NAME = 'loss'
@@ -44,8 +44,8 @@ def __init__(self, # pylint: disable=too-many-arguments
4444
dataset: Optional[el.AbstractDataset], log_dir: Optional[str], inputs: List[str], outputs: List[str],
4545
session_config: Optional[dict]=None, n_gpus: int=0, restore_from: Optional[str]=None,
4646
optimizer=None, freeze=False, loss_name: str=DEFAULT_LOSS_NAME, monitor: Optional[str]=None,
47-
restore_fallback: Optional[str]=None, clip_gradient: Optional[float]=None,
48-
**kwargs):
47+
restore_fallback: Optional[str]=None, clip_gradient: Optional[float]=None, profile: bool=False,
48+
keep_profiles: int=5, **kwargs):
4949
"""
5050
Create new emloop trainable TensorFlow model.
5151
@@ -82,6 +82,8 @@ def __init__(self, # pylint: disable=too-many-arguments
8282
:param monitor: monitor signal mean and variance of the tensors which names contain the specified value
8383
:param restore_fallback: ignored arg. (allows training from configs saved by emloop where it is added)
8484
:param clip_gradient: limit the absolute value of the gradient; set to None for no clipping
85+
:param profile: if true, profile the speed of model inference and save profiles to the specified log_dir
86+
:param keep_profiles: if true, profile the speed of model inference and save profiles to the specified log_dir
8587
:param kwargs: additional kwargs forwarded to :py:meth:`_create_model`
8688
"""
8789
super().__init__(dataset=dataset, log_dir=log_dir, restore_from=restore_from)
@@ -97,10 +99,17 @@ def __init__(self, # pylint: disable=too-many-arguments
9799
self._towers = [GraphTower(i, inputs, outputs, loss_name) for i in range(n_gpus)]
98100
if n_gpus == 0:
99101
self._towers.append(GraphTower(-1, inputs, outputs, loss_name))
100-
101102
logging.info('\tCreating TF model on %s GPU devices', n_gpus)
102103
self._graph = tf.Graph()
103104
self._session = self._create_session(session_config)
105+
106+
if profile and not log_dir:
107+
raise ValueError('log_dir has to be specified with profile set to True')
108+
109+
self._profile = profile
110+
if profile:
111+
self._profiler = Profiler(log_dir, keep_profiles, self._session)
112+
104113
dependencies = []
105114
with self._graph.as_default():
106115
if restore_from is None:
@@ -223,12 +232,14 @@ def run(self, batch: el.Batch, train: bool=False, stream: el.datasets.StreamWrap
223232
for output_name in self.output_names:
224233
fetches.append(tower[output_name])
225234

235+
run_fn = self._profiler.run if self._profile else self._session.run
236+
226237
# run the computational graph for one batch and allow buffering in the meanwhile
227238
if stream is not None:
228239
with stream.allow_buffering:
229-
outputs = self._session.run(fetches=fetches, feed_dict=feed_dict)
240+
outputs = run_fn(fetches, feed_dict)
230241
else:
231-
outputs = self._session.run(fetches=fetches, feed_dict=feed_dict)
242+
outputs = run_fn(fetches, feed_dict)
232243

233244
if train:
234245
outputs = outputs[1:]

emloop_tensorflow/tests/model_test.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,21 @@ def test_regularization():
419419
regularized_model2.run(good_batch, train=True)
420420

421421

422+
def test_profiling(tmpdir):
423+
"""Test whether profile is created."""
424+
model = TrainableModel(dataset=None, log_dir=tmpdir, **_IO, optimizer=_OPTIMIZER, profile=True, keep_profiles=10)
425+
batch = {'input': [[1]*10], 'target': [[0]*10]}
426+
427+
# test if one can train one model while the other remains intact
428+
for _ in range(1000):
429+
model.run(batch, train=True)
430+
431+
for i in range(10):
432+
assert path.exists(f"{tmpdir}/profile_{i}.json")
433+
434+
assert not path.exists(f"{tmpdir}/profile_11.json")
435+
436+
422437
#######################
423438
# TF Base Model Saver #
424439
#######################

emloop_tensorflow/utils/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
Module with TensorFlow util functions.
33
"""
44
from .reflection import create_activation, create_optimizer
5+
from .profiler import Profiler
56

6-
__all__ = ['create_activation', 'create_optimizer']
7+
__all__ = ['create_activation', 'create_optimizer', 'Profiler']
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import tensorflow as tf
2+
from tensorflow.python.client import timeline
3+
from typing import Dict
4+
import os
5+
6+
7+
class Profiler:
8+
"""
9+
Profiles tensorflow graphs and saves the profiles.
10+
"""
11+
12+
def __init__(self, log_dir: str, keep_profiles: int, session: tf.Session):
13+
"""
14+
:param log_dir: directory where profiles will be saved
15+
:param keep_profiles: how many profiles are saved
16+
"""
17+
self._log_dir = log_dir
18+
self._profile_counter = 0
19+
self._keep_profiles = keep_profiles
20+
self._run_options = tf.RunOptions(trace_level=tf.RunOptions.FULL_TRACE)
21+
self._session = session
22+
23+
def run(self, fetches: Dict, feed_dict: Dict):
24+
"""
25+
Evaluates the tensorflow graph with profiling, saves profile and returns outputs.
26+
27+
:param session: tensorflow session
28+
:param fetches: names of output tensors
29+
:param feed_dict: input tensors
30+
"""
31+
run_metadata = tf.RunMetadata()
32+
outputs = self._session.run(fetches=fetches, feed_dict=feed_dict,
33+
options=self._run_options, run_metadata=run_metadata)
34+
35+
with open(os.path.join(self._log_dir, f'profile_{self._profile_counter}.json'), 'w') as ofile:
36+
tl = timeline.Timeline(run_metadata.step_stats)
37+
ofile.write(tl.generate_chrome_trace_format())
38+
39+
self._profile_counter = (self._profile_counter + 1) % self._keep_profiles
40+
41+
return outputs

0 commit comments

Comments
 (0)