205 lines
6.9 KiB
Python
205 lines
6.9 KiB
Python
#
|
|
# SPDX-FileCopyrightText: Copyright (c) 1993-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
#
|
|
from collections import defaultdict
|
|
|
|
from polygraphy import mod, util
|
|
from polygraphy.common import TensorMetadata
|
|
from polygraphy.logger import G_LOGGER
|
|
|
|
tf = mod.lazy_import("tensorflow<2.0")
|
|
|
|
|
|
def load_graph(path):
|
|
"""
|
|
Loads a TensorFlow frozen model.
|
|
|
|
Args:
|
|
path (Union[str, tf.Graph, tf.GraphDef]):
|
|
A path to the frozen model, or a frozen TensorFlow graph or graphdef.
|
|
|
|
Returns:
|
|
tf.Graph: The TensorFlow graph
|
|
"""
|
|
if isinstance(path, tf.Graph):
|
|
return path
|
|
|
|
if isinstance(path, str):
|
|
graphdef = tf.compat.v1.GraphDef()
|
|
|
|
import google
|
|
|
|
try:
|
|
graphdef.ParseFromString(util.load_file(path, description="GraphDef"))
|
|
except google.protobuf.message.DecodeError:
|
|
G_LOGGER.backtrace()
|
|
G_LOGGER.critical(
|
|
f"Could not import TensorFlow GraphDef from: {path}. Is this a valid TensorFlow model?"
|
|
)
|
|
elif isinstance(path, tf.compat.v1.GraphDef):
|
|
graphdef = path
|
|
|
|
with tf.Graph().as_default() as graph:
|
|
tf.import_graph_def(graphdef, name="")
|
|
return graph
|
|
|
|
|
|
def find_nodes_by_ops(graphdef, ops):
|
|
ops = set(ops)
|
|
return [node for node in graphdef.node if any([op in node.op for op in ops])]
|
|
|
|
|
|
def map_node_outputs(graphdef):
|
|
def sanitize_input_name(input_name):
|
|
# Strip port information and control symbol
|
|
split_input = input_name.split(":")
|
|
if len(split_input) > 1:
|
|
split_input.pop(-1)
|
|
return ":".join(split_input).replace("^", "")
|
|
|
|
node_outputs = defaultdict(list)
|
|
for node in graphdef.node:
|
|
for input_name in node.input:
|
|
node_outputs[sanitize_input_name(input_name)].append(node)
|
|
return node_outputs
|
|
|
|
|
|
def get_tensor_metadata(tensors):
|
|
metadata = TensorMetadata()
|
|
for tensor in tensors:
|
|
try:
|
|
shape = [
|
|
elem.value if hasattr(elem, "value") else elem for elem in tensor.shape
|
|
]
|
|
except ValueError:
|
|
# Happens when rank is unknown
|
|
shape = None
|
|
metadata.add(tensor.name, dtype=tensor.dtype.as_numpy_dtype, shape=shape)
|
|
return metadata
|
|
|
|
|
|
def get_input_metadata(graph):
|
|
input_tensors = []
|
|
input_nodes = find_nodes_by_ops(graph.as_graph_def(), ["Placeholder", "FIFOQueue"])
|
|
G_LOGGER.verbose(
|
|
f"Found input tensors: {[f'{n.name}: {n.op}' for n in input_nodes]}"
|
|
)
|
|
for node in input_nodes:
|
|
input_tensors.append(graph.get_tensor_by_name(node.name + ":0"))
|
|
|
|
G_LOGGER.verbose(f"Retrieved TensorFlow input_tensors: {input_tensors}")
|
|
return get_tensor_metadata(input_tensors)
|
|
|
|
|
|
def get_output_metadata(graph, layerwise=False):
|
|
graphdef = graph.as_graph_def()
|
|
|
|
node_output_map = map_node_outputs(graphdef)
|
|
|
|
def is_output_node(node):
|
|
# Make sure that we're not using hanging nodes as outputs - must have at least one input.
|
|
if len(node_output_map[node.name]) != 0 or len(node.input) == 0:
|
|
return False
|
|
|
|
# Tensors with no shape cannot be outputs and TensorFlow doesn't like certain ops as outputs.
|
|
EXCLUDE_OPS = [
|
|
"Switch",
|
|
"FusedBatchNorm",
|
|
"Assert",
|
|
"NextIteration",
|
|
"Enter",
|
|
"LoopCond",
|
|
"Exit",
|
|
"Print",
|
|
"Assign",
|
|
"NoOp",
|
|
"ReadVariableOp",
|
|
"VarIsInitializedOp",
|
|
"Const",
|
|
]
|
|
|
|
# Additionally, we sometimes need to exclude entire namespaces e.g. while loops.
|
|
EXCLUDE_NAMESPACES = ["while", "Assert"]
|
|
|
|
if any([ex_op in node.op for ex_op in EXCLUDE_OPS]) or any(
|
|
[ns in node.name for ns in EXCLUDE_NAMESPACES]
|
|
):
|
|
G_LOGGER.extra_verbose(
|
|
f"Excluding {node.name}, op {node.op} is not a valid output op or is part of an excluded namespace (Note: excluded namespaces: {EXCLUDE_NAMESPACES})"
|
|
)
|
|
return False
|
|
|
|
return True
|
|
|
|
# For layerwise mode, every layer becomes an output.
|
|
if layerwise:
|
|
output_nodes = list(graphdef.node)
|
|
G_LOGGER.verbose(
|
|
f"Running in layerwise mode. Marking {len(output_nodes)} layers as potential outputs"
|
|
)
|
|
else:
|
|
output_nodes = [node for node in graphdef.node if is_output_node(node)]
|
|
G_LOGGER.extra_verbose(f"Found likely output nodes: {output_nodes}")
|
|
|
|
output_tensors = []
|
|
for node in output_nodes:
|
|
tensor_name = node.name + ":0"
|
|
try:
|
|
tensor = graph.get_tensor_by_name(tensor_name)
|
|
output_tensors.append(tensor)
|
|
except KeyError:
|
|
G_LOGGER.warning(f"Could not import: {tensor_name}. Skipping.")
|
|
if len(output_tensors) != len(output_nodes):
|
|
G_LOGGER.warning(
|
|
f"Excluded {len(output_nodes) - len(output_tensors)} ops that don't seem like outputs. Use -vv/--super-verbose, or set logging verbosity to EXTRA_VERBOSE to view them."
|
|
)
|
|
|
|
G_LOGGER.extra_verbose(
|
|
f"Found output op types in graph: {set(tensor.op.type for tensor in output_tensors)}"
|
|
)
|
|
G_LOGGER.verbose(f"Retrieved TensorFlow output_tensors: {output_tensors}")
|
|
return get_tensor_metadata(output_tensors)
|
|
|
|
|
|
def get_graph_output_names(graph):
|
|
return list(get_output_metadata(graph).keys())
|
|
|
|
|
|
def str_from_graph(graph, show_layers=None, show_attrs=None, show_weights=None):
|
|
show_layers = util.default(show_layers, False)
|
|
show_attrs = util.default(show_attrs, False)
|
|
show_weights = util.default(show_weights, False)
|
|
|
|
graph_str = ""
|
|
input_metadata = get_input_metadata(graph)
|
|
output_metadata = get_output_metadata(graph)
|
|
|
|
graph_str += f"---- {len(input_metadata)} Graph Inputs ----\n{input_metadata}\n\n"
|
|
graph_str += (
|
|
f"---- {len(output_metadata)} Graph Outputs ----\n{output_metadata}\n\n"
|
|
)
|
|
graph_str += f"---- {len(graph.as_graph_def().node)} Nodes ----\n"
|
|
if show_layers:
|
|
G_LOGGER.warning(
|
|
"Displaying layer information is unsupported for TensorFlow graphs. "
|
|
"Please use --show layers attrs weights if you would like to see the raw nodes"
|
|
)
|
|
if show_attrs or show_weights:
|
|
for node in graph.as_graph_def().node:
|
|
graph_str += str(node) + "\n"
|
|
graph_str += "\n"
|
|
return util.indent_block(graph_str, level=0)
|