1# Copyright 2017 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"""Library for arbitrary expression evaluation based on a debugger data dump.""" 16from __future__ import absolute_import 17from __future__ import division 18from __future__ import print_function 19 20import re 21 22import numpy as np # pylint: disable=unused-import 23 24from tensorflow.python.debug.lib import debug_data 25 26_DUMP_TENSOR_PATTERN = re.compile(r"`.*?`") 27_DEVICE_NAME_PREFIX_PATTERN = re.compile( 28 r"/job:(\w)+/replica:(\d)+/task:(\d)+/(\w)+:(\d)+:") 29_EXEC_INDEX_SUFFIX_PATTERN = re.compile(r"\[(\d)*\]$") 30 31_DEFAULT_DEBUG_OP = "DebugIdentity" 32 33 34def _parse_debug_tensor_name(debug_tensor_name): 35 # pylint: disable=line-too-long 36 """Parse a debug tensor name in a to-be-evaluated expression. 37 38 Args: 39 debug_tensor_name: name of the debug tensor, with or without 40 device name as a prefix, with or without debug op, with or 41 without '[<exec_index>]' as a suffix. 42 E.g., without device name prefix, without debug op suffix: 43 "hidden_0/MatMul:0" 44 E.g., with device name prefix: 45 "/job:worker/replica:0/task:1/gpu:0:hidden_0/MatMul:0" 46 E.g., with debug op suffix: 47 "hidden_0/MatMul:0:DebugNumericSummary" 48 E.g., with device name prefix and debug op suffix: 49 "/job:worker/replica:0/task:1/gpu:0:hidden_0/MatMul:0:DebugNumericSummary" 50 E.g., with device name prefix, debug op and an exec index: 51 "/job:worker/replica:0/task:1/gpu:0:hidden_0/MatMul:0:DebugNumericSummary[1]" 52 53 Returns: 54 device_name: If device name prefix exists, the device name; otherwise, 55 `None`. 56 node_name: Name of the node. 57 output_slot: Output slot index as an `int`. 58 debug_op: If the debug op suffix exists, the debug op name; otheriwse, 59 `None`. 60 exec_index: Execution index (applicable to cases in which a debug tensor 61 is computed multiple times in a `tf.Session.run` call, e.g., due to 62 `tf.while_loop`). If the exec_index suffix does not exist, this value 63 defaults to `0`. 64 65 Raises: 66 ValueError: If the input `debug_tensor_name` is malformed. 67 """ 68 # pylint: enable=line-too-long 69 device_prefix_match = re.match(_DEVICE_NAME_PREFIX_PATTERN, debug_tensor_name) 70 if device_prefix_match: 71 device_name = debug_tensor_name[ 72 device_prefix_match.start() : device_prefix_match.end() - 1] 73 debug_tensor_name = debug_tensor_name[device_prefix_match.end():] 74 else: 75 device_name = None 76 77 split_items = debug_tensor_name.split(":") 78 if len(split_items) not in (2, 3): 79 raise ValueError( 80 "The debug tensor name in the to-be-evaluated expression is malformed: " 81 "'%s'" % debug_tensor_name) 82 # TODO(cais): Provide examples of good debug tensor names in the error 83 # message. 84 85 exec_index_match = re.search(_EXEC_INDEX_SUFFIX_PATTERN, split_items[-1]) 86 if exec_index_match: 87 exec_index = int(split_items[-1][ 88 exec_index_match.start() + 1 : exec_index_match.end() - 1]) 89 split_items[-1] = split_items[-1][:exec_index_match.start()] 90 else: 91 exec_index = 0 92 93 if len(split_items) == 2: 94 node_name = split_items[0] 95 output_slot = int(split_items[1]) 96 debug_op = _DEFAULT_DEBUG_OP 97 else: 98 split_items = debug_tensor_name.split(":") 99 node_name = split_items[0] 100 output_slot = int(split_items[1]) 101 debug_op = split_items[2] 102 103 return device_name, node_name, output_slot, debug_op, exec_index 104 105 106class ExpressionEvaluator(object): 107 """Evaluates Python expressions using debug tensor values from a dump.""" 108 109 def __init__(self, dump): 110 """Constructor of ExpressionEvaluator. 111 112 Args: 113 dump: an instance of `DebugDumpDir`. 114 """ 115 self._dump = dump 116 self._cached_tensor_values = dict() 117 118 def evaluate(self, expression): 119 """Parse an expression. 120 121 Args: 122 expression: the expression to be parsed. 123 124 Returns: 125 The result of the evaluation. 126 127 Raises: 128 ValueError: If the value of one or more of the debug tensors in the 129 expression are not available. 130 """ 131 dump_tensors_iter = re.finditer(_DUMP_TENSOR_PATTERN, expression) 132 rewritten_expression = expression 133 for match in reversed(list(dump_tensors_iter)): 134 tensor_name = match.group(0)[1:-1].strip() 135 device_name, node_name, output_slot, debug_op, exec_index = ( 136 _parse_debug_tensor_name(tensor_name)) 137 if tensor_name not in self._cached_tensor_values: 138 try: 139 value = self._dump.get_tensors( 140 node_name, output_slot, debug_op, 141 device_name=device_name)[exec_index] 142 except debug_data.WatchKeyDoesNotExistInDebugDumpDirError: 143 raise ValueError( 144 "Eval failed due to the value of %s:%d:DebugIdentity being " 145 "unavailable" % (node_name, output_slot)) 146 self._cached_tensor_values[tensor_name] = value 147 rewritten_expression = ( 148 rewritten_expression[:match.start(0)] + 149 "self._cached_tensor_values['" + tensor_name + "']" + 150 rewritten_expression[match.end(0):]) 151 152 return eval(rewritten_expression) # pylint: disable=eval-used 153