Files
nvidia--tensorrt/samples/sampleSafeMNIST/sampleSafeMNISTInfer.cpp
T
wehub-resource-sync c8a779b1bb
Docker Image CI / build-ubuntu2004 (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:36:55 +08:00

455 lines
14 KiB
C++

/*
* SPDX-FileCopyrightText: Copyright (c) 1993-2026 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.
*/
//! \file sampleSafeMNISTInfer.cpp
//! \brief This file contains the implementation of the MNIST sample.
//!
//! It uses the prebuilt TensorRT engine to run inference on an input image of a digit.
//! It can be run with the following command line:
//! Command: ./sample_mnist_safe_infer
#include "NvInferSafeRuntime.h"
#include "safeCommon.h"
#include "safeErrorRecorder.h"
#include <algorithm>
#include <array>
#include <cassert>
#include <cmath>
#include <cuda_runtime_api.h>
#include <fstream>
#include <iostream>
#include <memory>
#include <numeric>
#include <random>
#include <string_view>
#include <thread>
#include <vector>
using namespace samplesSafeCommon;
namespace
{
//!
//! \brief Locate path to file by its filename. Will walk back MAX_DEPTH dirs from CWD to check for such a file path.
//!
std::string locateFile(std::string const& fileName, nvinfer2::safe::ISafeRecorder& recorder)
{
constexpr uint32_t MAX_DEPTH{10U};
std::array<std::string const, 2> const dirPatterns
= {std::string{"data/samples/mnist/"}, std::string{"data/mnist/"}};
std::string foundFile{};
for (auto const& dir : dirPatterns)
{
std::string file{dir + fileName};
bool found{false};
for (uint32_t i = 0U; i < MAX_DEPTH; i++)
{
std::ifstream checkFile(file);
found = checkFile.is_open();
if (found)
{
break;
}
file = "../" + file; // Try again in parent dir.
}
if (found)
{
foundFile = file;
break;
}
}
if (foundFile.empty())
{
safeLogError(recorder, "Could not find " + fileName + " in data/samples/mnist/ or data/mnist.");
safeLogError(recorder, "&&&& FAILED");
exit(EXIT_FAILURE);
}
return foundFile;
}
//!
//! \brief Reads the input data, preprocesses, and stores the result in a managed buffer.
//!
bool processInput(void* input, int32_t const inputFileIdx, nvinfer2::safe::ISafeRecorder& recorder)
{
std::stringstream ss;
constexpr int32_t kINPUT_H{28};
constexpr int32_t kINPUT_W{28};
// Read the digit file according to the inputFileIdx.
std::vector<uint8_t> fileData(kINPUT_H * kINPUT_W);
readPGMFile(locateFile(std::to_string(inputFileIdx) + ".pgm", recorder), fileData.data(), kINPUT_H, kINPUT_W);
// Print ASCII representation of digit.
ss << "Input:\n";
for (int32_t i = 0; i < kINPUT_H * kINPUT_W; i++)
{
ss << (" .:-=+*#%@"[fileData[i] / 26U]) << (((i + 1) % kINPUT_W) ? "" : "\n");
}
safeLogInfo(recorder, ss.str());
float* hostInputBuffer = static_cast<float*>(input);
std::copy(fileData.begin(), fileData.end(), hostInputBuffer);
// Normalize to 0-1 with background at 0
std::transform(hostInputBuffer, hostInputBuffer + kINPUT_H * kINPUT_W, hostInputBuffer,
[](float v) -> float { return 1.0f - v / 255.0f; });
return true;
}
//!
//! \brief Verifies that the output is correct and prints it.
//!
bool verifyOutput(void* output, int32_t groundTruthDigit, nvinfer2::safe::ISafeRecorder& recorder)
{
float* prob = static_cast<float*>(output);
// Print histogram of the output distribution.
safeLogInfo(recorder, "Output:");
float val{0.0f};
int32_t idx{0};
constexpr int32_t kDIGITS{10};
// Calculate Softmax
float sum{0.0f};
for (int32_t i = 0; i < kDIGITS; ++i)
{
prob[i] = exp(prob[i]);
sum += prob[i];
}
for (int32_t i = 0; i < kDIGITS; ++i)
{
std::stringstream ss;
prob[i] /= sum;
if (val < prob[i])
{
val = prob[i];
idx = i;
}
ss << " Prob " << i << " " << std::fixed << std::setw(5) << std::setprecision(4) << prob[i] << " Class " << i
<< ": " << std::string(int32_t(std::floor(prob[i] * 10 + 0.5f)), '*');
safeLogInfo(recorder, ss.str());
}
return (idx == groundTruthDigit && val > 0.9f);
}
//!
//! \brief Loads the enginePlanFile from engineFile and returns it.
//!
std::vector<char> loadEnginePlanFile(std::string const& engineFile, int& size, nvinfer2::safe::ISafeRecorder& recorder)
{
std::string const& filename = engineFile;
std::vector<char> gieModelStream;
std::ifstream file(filename, std::ios::binary);
if (!file.good())
{
safeLogError(recorder, "Could not open input engine file or file is empty. File name: " + filename);
return {};
}
file.seekg(0, std::ifstream::end);
size = file.tellg();
file.seekg(0, std::ifstream::beg);
gieModelStream.resize(size);
file.read(gieModelStream.data(), size);
file.close();
return gieModelStream;
}
//!
//! \brief Returns a random digit between 0 and 9
//!
int32_t getRandomDigit()
{
std::random_device rd;
std::default_random_engine generator{rd()};
std::uniform_int_distribution<int32_t> distribution(0, 9);
return distribution(generator);
}
//!
//! \brief Structure representing memory allocation for CUDA
//!
struct CudaMemory
{
void* hostPtr = nullptr;
void* devicePtr = nullptr;
size_t size = 0;
};
//!
//! \brief Do inference
//!
void doInferenceThread(nvinfer2::safe::ITRTGraph* graph, int8_t& ret_status, nvinfer2::safe::ISafeRecorder* recorder)
{
// Initialize to success; will be set to 0 on any error.
ret_status = 1;
int64_t nbIOs{};
SAFE_API_CALL(graph->getNbIOTensors(nbIOs), *recorder);
// This sample only has one input and one output.
SAFE_ASSERT(nbIOs == 2);
CudaMemory inputCudaMemory;
CudaMemory outputCudaMemory;
// Initialize main stream
cudaStream_t stream;
CUDA_CALL(cudaStreamCreateWithFlags(&stream, cudaStreamNonBlocking), *recorder);
// Setup as many auxiliary streams as the graph requires - destroyed at scope end.
auto auxStreamsDeleter = samplesSafeCommon::setUpAuxStreamsOn(*graph, *recorder);
// Pick a random digit to try to infer.
int32_t digit = getRandomDigit();
// Iterate through all input/output tensors
for (int64_t i = 0; i < nbIOs; ++i)
{
// Get the tensor name for the current I/O tensor
char const* tensorName;
SAFE_API_CALL(graph->getIOTensorName(tensorName, i), *recorder);
// Get tensor descriptor which contains metadata like size and I/O mode
nvinfer2::safe::TensorDescriptor desc;
SAFE_API_CALL(graph->getIOTensorDescriptor(desc, tensorName), *recorder);
// Allocate device and host memory for this tensor
void* deviceBuf = nullptr;
void* hostBuf = nullptr;
CUDA_CALL(cudaMalloc(&deviceBuf, desc.sizeInBytes), *recorder);
CUDA_CHECK(cudaHostAlloc(&hostBuf, desc.sizeInBytes, cudaHostAllocDefault));
if (desc.ioMode == TensorIOMode::kINPUT)
{
// Read the input data into the managed buffers.
processInput(hostBuf, digit, *recorder);
// Asynchronously copy data from host input buffers to device input buffers.
CUDA_CHECK(cudaMemcpyAsync(deviceBuf, hostBuf, desc.sizeInBytes, cudaMemcpyHostToDevice, stream));
inputCudaMemory = {hostBuf, deviceBuf, desc.sizeInBytes};
}
else if (desc.ioMode == TensorIOMode::kOUTPUT)
{
CUDA_CALL(cudaMemsetAsync(deviceBuf, 0, desc.sizeInBytes, stream), *recorder);
outputCudaMemory = {hostBuf, deviceBuf, desc.sizeInBytes};
}
else
{
safeLogError(*recorder, "Unexpected tensor IO mode");
ret_status = 0;
}
SAFE_ASSERT(desc.dataType == DataType::kFLOAT);
// Create a typed array for the tensor
nvinfer2::safe::TypedArray tensor
= nvinfer2::safe::TypedArray(static_cast<float*>(deviceBuf), desc.sizeInBytes);
SAFE_API_CALL(graph->setIOTensorAddress(tensorName, tensor), *recorder);
}
cudaEvent_t inputConsumedEvent;
cudaEventCreate(&inputConsumedEvent);
SAFE_API_CALL(graph->setInputConsumedEvent(inputConsumedEvent), *recorder);
// Run the graph
SAFE_API_CALL(graph->executeAsync(stream), *recorder);
cudaEvent_t retrievedEvent;
SAFE_API_CALL(graph->getInputConsumedEvent(retrievedEvent), *recorder);
SAFE_ASSERT(retrievedEvent != nullptr);
cudaEventSynchronize(retrievedEvent);
// Synchronize the network
SAFE_API_CALL(graph->sync(), *recorder);
// Asynchronously copy data from device output buffers to host output buffers.
CUDA_CHECK(cudaMemcpyAsync(
outputCudaMemory.hostPtr, outputCudaMemory.devicePtr, outputCudaMemory.size, cudaMemcpyDeviceToHost, stream));
// Wait for the work in the stream to complete.
CUDA_CHECK(cudaStreamSynchronize(stream));
// Check and print the output of the inference.
if (!verifyOutput(outputCudaMemory.hostPtr, digit, *recorder))
{
safeLogError(*recorder, "Failed to verify output");
ret_status = 0;
}
// Release stream and buffers.
CUDA_CHECK(cudaStreamDestroy(stream));
CUDA_CHECK(cudaFreeHost(inputCudaMemory.hostPtr));
CUDA_CHECK(cudaFreeHost(outputCudaMemory.hostPtr));
CUDA_CHECK(cudaFree(inputCudaMemory.devicePtr));
CUDA_CHECK(cudaFree(outputCudaMemory.devicePtr));
}
//!
//! \brief The SampleSafeMNISTInferArgs struct stores the additional arguments required by the sample
//!
struct SampleSafeMNISTInferArgs
{
std::string engineFileName{"safe_mnist.engine"};
int32_t threads{1};
bool help{false};
};
//!
//! \brief Runs the TensorRT inference engine for this sample.
//!
//! \details This function is the main execution function of the sample. It allocates
//! the buffer, sets inputs, executes the engine, and verifies the output.
//!
bool doInference(SampleSafeMNISTInferArgs const& args)
{
int32_t const nbThreads = args.threads;
std::vector<int8_t> ret_status(nbThreads);
std::vector<std::unique_ptr<sample::SampleSafeRecorder>> recorders(nbThreads);
for (int32_t i = 0; i < nbThreads; ++i)
{
recorders[i] = std::make_unique<sample::SampleSafeRecorder>(nvinfer2::safe::Severity::kINFO, i);
}
// Load safe engine blob
int32_t engineFileSize = 0;
auto gieModelStream = loadEnginePlanFile(args.engineFileName, engineFileSize, *recorders[0]);
SAFE_ASSERT(engineFileSize != 0);
// Configure executor(s)
std::vector<nvinfer2::safe::ITRTGraph*> graphs(nbThreads);
SAFE_API_CALL(nvinfer2::safe::createTRTGraph(graphs[0], gieModelStream.data(), engineFileSize, *recorders[0], true),
*recorders[0]);
for (int32_t i = 1; i < nbThreads; ++i)
{
SAFE_API_CALL(graphs[0]->clone(graphs[i], *recorders[i]), *recorders[0]);
}
// Run the graphs in independent threads
std::vector<std::thread> threads(nbThreads);
for (int32_t i = 0; i < nbThreads; ++i)
{
threads[i] = std::thread{doInferenceThread, graphs[i], std::ref(ret_status[i]), recorders[i].get()};
}
for (int32_t i = 0; i < nbThreads; ++i)
{
threads[i].join();
if (!ret_status[i])
{
return false;
}
}
for (int32_t i = 0; i < nbThreads; ++i)
{
SAFE_API_CALL(nvinfer2::safe::destroyTRTGraph(graphs[i]), *recorders[i]);
graphs[i] = nullptr;
}
return true;
}
//!
//! \brief This function parses arguments specific to the sample
//!
bool parseSampleSafeMNISTInferArgs(SampleSafeMNISTInferArgs& args, int32_t argc, char* argv[])
{
for (int32_t i = 1; i < argc; ++i)
{
std::string const arg = argv[i];
if (auto const value = parseString(arg, "loadEngine"))
{
args.engineFileName = *value;
}
else if (auto const value = parseString(arg, "threads"))
{
args.threads = std::stoi(*value);
if (args.threads <= 0)
{
SAFE_LOG << "Number of threads must be > 0, got: " << arg << "\n";
return false;
}
}
else if (parseBool(arg, "help", 'h'))
{
args.help = true;
}
else
{
SAFE_LOG << "Invalid Argument: " << arg << "\n";
return false;
}
}
return true;
}
//!
//! \brief Prints the help information for running this sample.
//!
void printHelpInfo()
{
SampleSafeMNISTInferArgs const defArgs{};
std::cout << R"(Usage: sample_mnist_safe_infer [options]
Options:
--help, -h Print this message and exit.
--loadEngine=FILE Load serialized engine from FILE (default = )"
<< defArgs.engineFileName << R"().
--threads=N Run inference in N threads concurrently (default = )"
<< defArgs.threads << R"().
)";
}
} // namespace
int32_t main(int32_t argc, char** argv)
{
safetyCompliance::setPromgrAbility();
SampleSafeMNISTInferArgs args;
bool argsOK = parseSampleSafeMNISTInferArgs(args, argc, argv);
if (!argsOK)
{
printHelpInfo();
return EXIT_FAILURE;
}
if (args.help)
{
printHelpInfo();
return EXIT_SUCCESS;
}
// Initialize SafeCuda before any other Cuda APIs are called. This may be skipped if createInferRuntime() is called
// first as per DEEPLRN_RES_116
safetyCompliance::initSafeCuda();
if (!isSmSafe())
{
SAFE_LOG << "Skip safe mode test on unsupported platforms." << std::endl;
return EXIT_SUCCESS;
}
TestResult result = doInference(args) ? TestResult::kPASSED : TestResult::kFAILED;
reportTestResult("TensorRT.sample_mnist_safe_infer", result, argc, argv);
return EXIT_SUCCESS;
}