1# Copyright 2016 The TensorFlow Authors. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14# ==============================================================================
15"""Model Analyzer.
16
17Analyze model, including shape, params, time, memory, structure, etc.
18"""
19from __future__ import absolute_import
20from __future__ import division
21from __future__ import print_function
22
23import sys
24
25import six
26
27from google.protobuf import message
28from tensorflow.core.profiler import tfprof_options_pb2
29from tensorflow.core.profiler import tfprof_output_pb2
30from tensorflow.python.eager import context
31from tensorflow.python.framework import errors
32from tensorflow.python.framework import ops
33from tensorflow.python.profiler import option_builder
34from tensorflow.python.profiler import tfprof_logger
35from tensorflow.python.util import _pywrap_tfprof as print_mdl
36from tensorflow.python.util.tf_export import tf_export
37
38_DEFAULT_PROFILE_OPTIONS = 0
39_DEFAULT_ADVISE_OPTIONS = 0
40
41# The following options are for 'advise' cmd.
42# Show all advice.
43ALL_ADVICE = {
44    'ExpensiveOperationChecker': {},
45    'AcceleratorUtilizationChecker': {},
46    'JobChecker': {},  # Only available internally.
47    'OperationChecker': {},
48}
49
50
51def _graph_string(graph):
52  """Helper to serialize a graph to string."""
53  if graph:
54    return graph.as_graph_def(add_shapes=True).SerializeToString()
55  else:
56    return b''
57
58
59def _build_options(options):
60  """Build tfprof.OptionsProto.
61
62  Args:
63    options: A dictionary of options.
64  Returns:
65    tfprof.OptionsProto.
66  """
67  opts = tfprof_options_pb2.OptionsProto()
68  opts.max_depth = options.get('max_depth', 10)
69  opts.min_bytes = options.get('min_bytes', 0)
70  opts.min_peak_bytes = options.get('min_peak_bytes', 0)
71  opts.min_residual_bytes = options.get('min_residual_bytes', 0)
72  opts.min_output_bytes = options.get('min_output_bytes', 0)
73  opts.min_micros = options.get('min_micros', 0)
74  opts.min_accelerator_micros = options.get('min_accelerator_micros', 0)
75  opts.min_cpu_micros = options.get('min_cpu_micros', 0)
76  opts.min_params = options.get('min_params', 0)
77  opts.min_float_ops = options.get('min_float_ops', 0)
78  opts.min_occurrence = options.get('min_occurrence', 0)
79
80  opts.step = options.get('step', -1)
81
82  opts.order_by = options.get('order_by', 'name')
83
84  for p in options.get('account_type_regexes', []):
85    opts.account_type_regexes.append(p)
86  for p in options.get('start_name_regexes', []):
87    opts.start_name_regexes.append(p)
88  for p in options.get('trim_name_regexes', []):
89    opts.trim_name_regexes.append(p)
90  for p in options.get('show_name_regexes', []):
91    opts.show_name_regexes.append(p)
92  for p in options.get('hide_name_regexes', []):
93    opts.hide_name_regexes.append(p)
94  opts.account_displayed_op_only = options.get('account_displayed_op_only',
95                                               False)
96
97  for p in options.get('select', []):
98    opts.select.append(p)
99
100  opts.output = options.get('output', 'stdout')
101  opts.dump_to_file = options.get('dump_to_file', '')
102
103  return opts
104
105
106def _build_advisor_options(options):
107  """Build tfprof.AdvisorOptionsProto.
108
109  Args:
110    options: A dictionary of options. See ALL_ADVICE example.
111  Returns:
112    tfprof.AdvisorOptionsProto.
113  """
114  opts = tfprof_options_pb2.AdvisorOptionsProto()
115  if options is None:
116    return opts
117  for checker, checker_opts in six.iteritems(options):
118    checker_ops_pb = tfprof_options_pb2.AdvisorOptionsProto.CheckerOption()
119    for k, v in six.iteritems(checker_opts):
120      checker_ops_pb[k] = v
121    opts.checkers[checker].MergeFrom(checker_ops_pb)
122  return opts
123
124
125@tf_export(v1=['profiler.Profiler'])
126class Profiler(object):
127  """TensorFlow multi-step profiler.
128
129  https://github.com/tensorflow/tensorflow/tree/master/tensorflow/core/profiler/README.md
130
131  ```python
132  Typical use case:
133    # Currently we are only allowed to create 1 profiler per process.
134    profiler = Profiler(sess.graph)
135
136    for i in xrange(total_steps):
137      if i % 10000 == 0:
138        run_meta = tf.compat.v1.RunMetadata()
139        _ = sess.run(...,
140                     options=tf.compat.v1.RunOptions(
141                         trace_level=tf.RunOptions.FULL_TRACE),
142                     run_metadata=run_meta)
143        profiler.add_step(i, run_meta)
144
145        # Profile the parameters of your model.
146        profiler.profile_name_scope(options=(option_builder.ProfileOptionBuilder
147            .trainable_variables_parameter()))
148
149        # Or profile the timing of your model operations.
150        opts = option_builder.ProfileOptionBuilder.time_and_memory()
151        profiler.profile_operations(options=opts)
152
153        # Or you can generate a timeline:
154        opts = (option_builder.ProfileOptionBuilder(
155                option_builder.ProfileOptionBuilder.time_and_memory())
156                .with_step(i)
157                .with_timeline_output(filename).build())
158        profiler.profile_graph(options=opts)
159      else:
160        _ = sess.run(...)
161    # Auto detect problems and generate advice.
162    profiler.advise()
163  ```
164  """
165
166  def __init__(self, graph=None, op_log=None):
167    """Constructor.
168
169    Args:
170      graph: tf.Graph. If None and eager execution is not enabled, use
171          default graph.
172      op_log: optional. tensorflow::tfprof::OpLogProto proto. Used to define
173          extra op types.
174    """
175    if not graph and not context.executing_eagerly():
176      graph = ops.get_default_graph()
177    self._coverage = 0.0
178    self._graph = graph
179    # pylint: disable=protected-access
180    op_log = tfprof_logger.merge_default_with_oplog(
181        self._graph, op_log=op_log)
182    # pylint: enable=protected-access
183    print_mdl.NewProfiler(
184        _graph_string(self._graph), op_log.SerializeToString())
185
186  def __del__(self):
187    print_mdl.DeleteProfiler()
188
189  def add_step(self, step, run_meta):
190    """Add statistics of a step.
191
192    Args:
193      step: int, An id used to group one or more different `run_meta` together.
194          When profiling with the profile_xxx APIs, user can use the `step`
195          id in the `options` to profile these `run_meta` together.
196      run_meta: RunMetadata proto that contains statistics of a session run.
197    """
198    # pylint: disable=protected-access
199    op_log = tfprof_logger.merge_default_with_oplog(
200        self._graph, run_meta=run_meta)
201    # pylint: enable=protected-access
202    # TODO(xpan): P1: Better to find the current graph.
203    self._coverage = print_mdl.AddStep(step, _graph_string(self._graph),
204                                       run_meta.SerializeToString(),
205                                       op_log.SerializeToString())
206
207  def profile_python(self, options):
208    """Profile the statistics of the Python codes.
209
210      By default, it shows the call stack from root. To avoid
211      redundant output, you may use options to filter as below
212        options['show_name_regexes'] = ['.*my_code.py.*']
213
214    Args:
215      options: A dict of options. See core/profiler/g3doc/options.md.
216    Returns:
217      a MultiGraphNodeProto that records the results.
218    """
219    opts = _build_options(options)
220    tfprof_node = tfprof_output_pb2.MultiGraphNodeProto()
221    try:
222      tfprof_node.ParseFromString(
223          print_mdl.Profile('code'.encode('utf-8'), opts.SerializeToString()))
224    except message.DecodeError as e:
225      sys.stderr.write('Cannot parse returned proto: %s.\n' % e)
226    return tfprof_node
227
228  def profile_operations(self, options):
229    """Profile the statistics of the Operation types (e.g. MatMul, Conv2D).
230
231    Args:
232      options: A dict of options. See core/profiler/g3doc/options.md.
233    Returns:
234      a MultiGraphNodeProto that records the results.
235    """
236    opts = _build_options(options)
237    tfprof_node = tfprof_output_pb2.MultiGraphNodeProto()
238    try:
239      tfprof_node.ParseFromString(
240          print_mdl.Profile('op'.encode('utf-8'), opts.SerializeToString()))
241    except message.DecodeError as e:
242      sys.stderr.write('Cannot parse returned proto: %s.\n' % e)
243    return tfprof_node
244
245  def profile_name_scope(self, options):
246    """Profile the statistics of graph nodes, organized by name scope.
247
248    Args:
249      options: A dict of options. See core/profiler/g3doc/options.md.
250    Returns:
251      a GraphNodeProto that records the results.
252    """
253    opts = _build_options(options)
254    tfprof_node = tfprof_output_pb2.GraphNodeProto()
255    try:
256      tfprof_node.ParseFromString(
257          print_mdl.Profile('scope'.encode('utf-8'), opts.SerializeToString()))
258    except message.DecodeError as e:
259      sys.stderr.write('Cannot parse returned proto: %s.\n' % e)
260    return tfprof_node
261
262  def profile_graph(self, options):
263    """Profile the statistics of graph nodes, organized by dataflow graph.
264
265    Args:
266      options: A dict of options. See core/profiler/g3doc/options.md.
267    Returns:
268      a GraphNodeProto that records the results.
269    """
270    opts = _build_options(options)
271    tfprof_node = tfprof_output_pb2.GraphNodeProto()
272    try:
273      tfprof_node.ParseFromString(
274          print_mdl.Profile('graph'.encode('utf-8'), opts.SerializeToString()))
275    except message.DecodeError as e:
276      sys.stderr.write('Cannot parse returned proto: %s.\n' % e)
277    return tfprof_node
278
279  def advise(self, options):
280    """Automatically detect problems and generate reports.
281
282    Args:
283      options: A dict of options. See ALL_ADVICE example above.
284    Returns:
285      An Advise proto that contains the reports from all checkers.
286    """
287    advise_pb = tfprof_output_pb2.AdviceProto()
288    opts = _build_advisor_options(options)
289    advise_pb.ParseFromString(
290        print_mdl.Profile('advise'.encode('utf-8'), opts.SerializeToString()))
291    return advise_pb
292
293  def serialize_to_string(self):
294    """Serialize the ProfileProto to a binary string.
295
296      Users can write it to file for offline analysis by tfprof commandline
297      or graphical interface.
298
299    Returns:
300      ProfileProto binary string.
301    """
302    return print_mdl.SerializeToString()
303
304  def _write_profile(self, filename):
305    """Writes the profile to a file."""
306    print_mdl.WriteProfile(filename)
307
308
309@tf_export(v1=['profiler.profile'])
310def profile(graph=None,
311            run_meta=None,
312            op_log=None,
313            cmd='scope',
314            options=_DEFAULT_PROFILE_OPTIONS):
315  """Profile model.
316
317    Tutorials and examples can be found in:
318    https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/profiler/g3doc/python_api.md
319
320  Args:
321    graph: tf.Graph. If None and eager execution is not enabled, use
322        default graph.
323    run_meta: optional tensorflow.RunMetadata proto. It is necessary to
324        to support run time information profiling, such as time and memory.
325    op_log: tensorflow.tfprof.OpLogProto proto. User can assign "types" to
326        graph nodes with op_log. "types" allow user to flexibly group and
327        account profiles using options['accounted_type_regexes'].
328    cmd: string. Either 'op', 'scope', 'graph' or 'code'.
329        'op' view organizes profile using operation type. (e.g. MatMul)
330        'scope' view organizes profile using graph node name scope.
331        'graph' view organizes profile using graph node inputs/outputs.
332        'code' view organizes profile using Python call stack.
333    options: A dict of options. See core/profiler/g3doc/options.md.
334  Returns:
335    If cmd is 'scope' or 'graph', returns GraphNodeProto proto.
336    If cmd is 'op' or 'code', returns MultiGraphNodeProto proto.
337    Side effect: stdout/file/timeline.json depending on options['output']
338  """
339  if not graph and not context.executing_eagerly():
340    graph = ops.get_default_graph()
341
342  if options == _DEFAULT_PROFILE_OPTIONS:
343    options = (option_builder.ProfileOptionBuilder
344               .trainable_variables_parameter())
345  # pylint: disable=protected-access
346  op_log = tfprof_logger.merge_default_with_oplog(
347      graph, op_log, run_meta, add_trace=cmd == 'code')
348  # pylint: enable=protected-access
349
350  opts = _build_options(options)
351
352  run_meta_str = run_meta.SerializeToString() if run_meta else b''
353
354  graph_str = _graph_string(graph)
355
356  if cmd == 'code' or cmd == 'op':
357    tfprof_node = tfprof_output_pb2.MultiGraphNodeProto()
358    ret = print_mdl.PrintModelAnalysis(graph_str, run_meta_str,
359                                       op_log.SerializeToString(),
360                                       cmd.encode('utf-8'),
361                                       opts.SerializeToString())
362    try:
363      tfprof_node.ParseFromString(ret)
364    except message.DecodeError as e:
365      sys.stderr.write('Cannot parse returned proto: %s.\n' % e)
366
367  elif cmd == 'graph' or cmd == 'scope':
368    tfprof_node = tfprof_output_pb2.GraphNodeProto()
369    ret = print_mdl.PrintModelAnalysis(graph_str, run_meta_str,
370                                       op_log.SerializeToString(),
371                                       cmd.encode('utf-8'),
372                                       opts.SerializeToString())
373    try:
374      tfprof_node.ParseFromString(ret)
375    except message.DecodeError as e:
376      sys.stderr.write('Cannot parse returned proto: %s.\n' % e)
377  else:
378    raise errors.InvalidArgumentError(
379        None, None, 'unknown cmd: %s\n' % cmd)
380
381  return tfprof_node
382
383
384@tf_export(v1=['profiler.advise'])
385def advise(graph=None, run_meta=None, options=_DEFAULT_ADVISE_OPTIONS):
386  """Auto profile and advise.
387
388    Builds profiles and automatically check anomalies of various
389    aspects. For more details:
390    https://github.com/tensorflow/tensorflow/tree/master/tensorflow/core/profiler/README.md
391
392  Args:
393    graph: tf.Graph. If None and eager execution is not enabled, use
394        default graph.
395    run_meta: optional tensorflow.RunMetadata proto. It is necessary to
396        to support run time information profiling, such as time and memory.
397    options: see ALL_ADVICE example above. Default checks everything.
398  Returns:
399    Returns AdviceProto proto
400  """
401  if not graph and not context.executing_eagerly():
402    graph = ops.get_default_graph()
403
404  if options == _DEFAULT_ADVISE_OPTIONS:
405    options = ALL_ADVICE.copy()
406
407  # pylint: disable=protected-access
408  op_log = tfprof_logger.merge_default_with_oplog(
409      graph, None, run_meta, add_trace=True)
410  # pylint: enable=protected-access
411
412  run_meta_str = run_meta.SerializeToString() if run_meta else b''
413
414  opts = _build_advisor_options(options)
415  ret = tfprof_output_pb2.AdviceProto()
416  ret.ParseFromString(
417      print_mdl.PrintModelAnalysis(
418          _graph_string(graph), run_meta_str, op_log.SerializeToString(),
419          'advise'.encode('utf-8'), opts.SerializeToString()))
420  return ret
421