277 lines
10 KiB
Python
277 lines
10 KiB
Python
#
|
|
# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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.
|
|
#
|
|
|
|
|
|
import os
|
|
import argparse
|
|
import numpy as np
|
|
import tensorrt as trt
|
|
import pycuda.driver as cuda
|
|
|
|
# If you face the following issue:
|
|
# "pycuda._driver.LogicError: explicit_context_dependent failed: invalid device context - no currently active context?"
|
|
# Add "import pycuda.autoinit", this is needed to initialize cuda!
|
|
import pycuda.autoinit
|
|
import tensorflow as tf
|
|
from examples.data.data_loader import load_data_tfrecord_tf, load_image_np, _SUPPORTED_MODEL_NAMES
|
|
|
|
TRT_DYNAMIC_DIM = -1
|
|
|
|
|
|
class HostDeviceMem(object):
|
|
"""Simple helper data class to store Host and Device memory."""
|
|
|
|
def __init__(self, host_mem, device_mem):
|
|
self.host = host_mem
|
|
self.device = device_mem
|
|
|
|
def __str__(self):
|
|
return "Host:\n" + str(self.host) + "\nDevice:\n" + str(self.device)
|
|
|
|
def __repr__(self):
|
|
return self.__str__()
|
|
|
|
|
|
def allocate_buffers(engine: trt.ICudaEngine, batch_size: int) -> [list, list, list]:
|
|
"""
|
|
Function to allocate buffers and bindings for TensorRT inference.
|
|
|
|
Args:
|
|
engine (trt.ICudaEngine):
|
|
batch_size (int): batch size to be used during inference.
|
|
|
|
Returns:
|
|
inputs (List): list of input buffers.
|
|
outputs (List): list of output buffers.
|
|
dbindings (List): list of device bindings.
|
|
"""
|
|
inputs = []
|
|
outputs = []
|
|
dbindings = []
|
|
|
|
for binding in engine:
|
|
binding_shape = engine.get_binding_shape(binding)
|
|
if binding_shape[0] == TRT_DYNAMIC_DIM: # dynamic shape
|
|
size = batch_size * abs(trt.volume(binding_shape))
|
|
else:
|
|
size = abs(trt.volume(binding_shape))
|
|
dtype = trt.nptype(engine.get_binding_dtype(binding))
|
|
# Allocate host and device buffers
|
|
host_mem = cuda.pagelocked_empty(size, dtype)
|
|
device_mem = cuda.mem_alloc(host_mem.nbytes)
|
|
# Append the device buffer to device bindings
|
|
dbindings.append(int(device_mem))
|
|
|
|
# Append to the appropriate list (input/output)
|
|
if engine.binding_is_input(binding):
|
|
inputs.append(HostDeviceMem(host_mem, device_mem))
|
|
else:
|
|
outputs.append(HostDeviceMem(host_mem, device_mem))
|
|
|
|
return inputs, outputs, dbindings
|
|
|
|
|
|
def infer(
|
|
engine_path: str,
|
|
val_batches,
|
|
batch_size: int = 8,
|
|
top_k_value: int = 1,
|
|
) -> None:
|
|
"""
|
|
Performs inference in TensorRT engine.
|
|
|
|
Args:
|
|
engine_path (str): path to the TensorRT engine.
|
|
val_batches (tf.data.Dataset): validation dataset (batches).
|
|
batch_size (int): batch size used for inference and dataset batch splitting.
|
|
top_k_value (int): value of `K` for the top K predictions used in the accuracy calculation.
|
|
|
|
Raises:
|
|
RuntimeError: raised when loading images in the host fails.
|
|
"""
|
|
|
|
def override_shape(shape: tuple) -> tuple:
|
|
"""Overrides batch dimension if dynamic."""
|
|
if TRT_DYNAMIC_DIM in shape:
|
|
shape = tuple(
|
|
[batch_size if dim == TRT_DYNAMIC_DIM else dim for dim in shape]
|
|
)
|
|
return shape
|
|
|
|
# Open engine as runtime
|
|
with open(engine_path, "rb") as f, trt.Runtime(
|
|
trt.Logger(trt.Logger.ERROR)
|
|
) as runtime:
|
|
engine = runtime.deserialize_cuda_engine(f.read())
|
|
|
|
# Allocate buffers and create a CUDA stream.
|
|
inputs, outputs, dbindings = allocate_buffers(engine, batch_size)
|
|
|
|
# Initiate test_accuracy
|
|
test_accuracy = tf.keras.metrics.SparseTopKCategoricalAccuracy(
|
|
k=top_k_value, name="top_k_accuracy", dtype=tf.float32
|
|
)
|
|
test_accuracy.reset_states()
|
|
|
|
# Contexts are used to perform inference.
|
|
with engine.create_execution_context() as context:
|
|
|
|
# Resolves dynamic shapes in the context
|
|
for binding in engine:
|
|
binding_idx = engine.get_binding_index(binding)
|
|
binding_shape = engine.get_binding_shape(binding_idx)
|
|
if engine.binding_is_input(binding_idx):
|
|
binding_shape = override_shape(binding_shape)
|
|
context.set_binding_shape(binding_idx, binding_shape)
|
|
|
|
if isinstance(val_batches, tf.Tensor):
|
|
# Load images in Host (flatten and copy to page-locked buffer in Host)
|
|
data = val_batches.numpy().astype(np.float32).ravel()
|
|
pagelocked_buffer = inputs[0].host
|
|
np.copyto(pagelocked_buffer, data)
|
|
inp = inputs[0]
|
|
# Transfer input data from Host to Device (GPU)
|
|
cuda.memcpy_htod(inp.device, inp.host)
|
|
# Run inference
|
|
context.execute_v2(dbindings)
|
|
# Transfer predictions back to Host from GPU
|
|
out = outputs[0]
|
|
cuda.memcpy_dtoh(out.host, out.device)
|
|
|
|
softmax_output = np.array(out.host)
|
|
top1_idx = np.argmax(softmax_output)
|
|
output_confidence = softmax_output[top1_idx]
|
|
print("Top-1 Index of the image : {} Confidence: {}".format(top1_idx, output_confidence))
|
|
|
|
elif isinstance(val_batches, tf.data.Dataset):
|
|
# Loop over number of steps to evaluate entire validation dataset
|
|
for step, example in enumerate(val_batches):
|
|
images, labels = example
|
|
if step % 100 == 0 and step != 0:
|
|
print(
|
|
"Evaluating batch {}: {:.4f}".format(
|
|
step, test_accuracy.result()
|
|
)
|
|
)
|
|
try:
|
|
# Load images in Host (flatten and copy to page-locked buffer in Host)
|
|
data = images.numpy().astype(np.float32).ravel()
|
|
pagelocked_buffer = inputs[0].host
|
|
np.copyto(pagelocked_buffer, data)
|
|
except RuntimeError:
|
|
raise RuntimeError(
|
|
"Failed to load images in Host at step {}".format(step)
|
|
)
|
|
|
|
inp = inputs[0]
|
|
# Transfer input data from Host to Device (GPU)
|
|
cuda.memcpy_htod(inp.device, inp.host)
|
|
# Run inference
|
|
context.execute_v2(dbindings)
|
|
# Transfer predictions back to Host from GPU
|
|
out = outputs[0]
|
|
cuda.memcpy_dtoh(out.host, out.device)
|
|
|
|
# Split 1-D output of length N*labels into 2-D array of (N, labels)
|
|
batch_outs = np.array(np.split(np.array(out.host), batch_size))
|
|
# Update test accuracy
|
|
test_accuracy.update_state(labels, batch_outs)
|
|
|
|
# Print final accuracy and save to log file
|
|
print("\n======================================\n")
|
|
result_str = "Top-{} accuracy: {:.4f}\n".format(
|
|
top_k_value, test_accuracy.result()
|
|
)
|
|
print(result_str)
|
|
# Save logs to file
|
|
results_dir = "/".join(args.engine.split("/")[:-1])
|
|
with open(os.path.join(results_dir, args.log_file), "w") as log_file:
|
|
log_file.write(result_str)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(
|
|
description="Run inference on TensorRT engines for Imagenet-based Classification models."
|
|
)
|
|
parser.add_argument(
|
|
"-e", "--engine", type=str, required=True, help="Path to TensorRT engine"
|
|
)
|
|
parser.add_argument(
|
|
"--image", type=str, help="Path to an image to perform single image inference"
|
|
)
|
|
parser.add_argument(
|
|
"-m",
|
|
"--model_name",
|
|
type=str,
|
|
default="resnet_v1",
|
|
help="Name of the model, needed to choose the appropriate input pre-processing."
|
|
"Options include {}".format(_SUPPORTED_MODEL_NAMES),
|
|
)
|
|
parser.add_argument(
|
|
"-d",
|
|
"--data_dir",
|
|
default="/media/Data/ImageNet/train-val-tfrecord",
|
|
type=str,
|
|
help="Path to directory of input images in tfrecord format (val data).",
|
|
)
|
|
parser.add_argument(
|
|
"-k",
|
|
"--top_k_value",
|
|
default=1,
|
|
type=int,
|
|
help="Value of `K` for the top-K predictions used in the accuracy calculation.",
|
|
)
|
|
parser.add_argument(
|
|
"-b",
|
|
"--batch_size",
|
|
default=1,
|
|
type=int,
|
|
help="Number of inputs to send in parallel (up to max batch size of engine).",
|
|
)
|
|
parser.add_argument(
|
|
"--log_file",
|
|
type=str,
|
|
default="engine_accuracy.log",
|
|
help="Filename to save logs.",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
if args.model_name not in _SUPPORTED_MODEL_NAMES:
|
|
raise ValueError(
|
|
"Invalid model name ",
|
|
args.model_name,
|
|
" provided. Please select among {}".format(_SUPPORTED_MODEL_NAMES),
|
|
)
|
|
|
|
# Load the test data and pre-process input
|
|
val_batches = None
|
|
if args.image:
|
|
val_batches = load_image_np(args.image, args.model_name)
|
|
else:
|
|
data_batches = load_data_tfrecord_tf(
|
|
data_dir=args.data_dir, batch_size=args.batch_size, model_name=args.model_name
|
|
)
|
|
val_batches = data_batches["validation"]
|
|
|
|
# Perform inference
|
|
infer(
|
|
args.engine,
|
|
val_batches,
|
|
batch_size=args.batch_size,
|
|
top_k_value=args.top_k_value,
|
|
)
|