Timsty's picture
Add files using upload-large-folder tool
f53e03f verified
Raw
History Blame Contribute Delete
27.1 kB
#include "InferenceEngine.h"
#include "Log.h"
#include "picosha2.h"
#include <fstream>
#include <array>
#include <bitset>
#include <vector>
#include <map>
#include <iostream>
#include <NvInfer.h>
#include <NvOnnxParser.h>
namespace
{
class Logger : public nvinfer1::ILogger
{
public:
Logger( bool verbose )
: m_flags( UINT8_MAX )
{
m_flags.set( static_cast<size_t>( Severity::kVERBOSE ), verbose );
}
void log( Severity severity, const char* msg ) noexcept override
{
if ( !m_flags.test( static_cast<size_t>( severity ) ) )
return;
switch ( severity )
{
case Severity::kINFO:
case Severity::kVERBOSE:
LOG_INFO( msg );
break;
case Severity::kWARNING:
LOG_WARNING( msg );
break;
case Severity::kERROR:
case Severity::kINTERNAL_ERROR:
LOG_ERROR( msg );
break;
}
}
void log( cudaError_t error )
{
if ( error == cudaError_t::cudaSuccess )
return;
LOG_ERROR( "CUDA failed with error [ " + std::to_string( error ) + " : " + cudaGetErrorName( error ) + " ]: " + cudaGetErrorString( error ) );
}
private:
static constexpr uint8_t NB_FLAGS = static_cast<uint8_t>( Severity::kVERBOSE ) + 1;
std::bitset<NB_FLAGS> m_flags;
};
Logger s_logger(false);
bool FileExists( const std::string& filepath )
{
return std::ifstream( filepath.c_str() ).good();
}
std::string GetCudaDeviceName( int deviceID )
{
int deviceCount = 0;
cudaGetDeviceCount( &deviceCount );
if ( deviceID >= deviceCount )
return std::string();
cudaDeviceProp prop;
cudaGetDeviceProperties( &prop, deviceID );
return std::string( prop.name );
}
int SizeOfTensorDataType( nvinfer1::DataType dataType )
{
int size = -1;
switch ( dataType )
{
case nvinfer1::DataType::kINT64:
size = sizeof( uint64_t );
break;
case nvinfer1::DataType::kINT32:
case nvinfer1::DataType::kFLOAT:
size = sizeof( uint32_t );
break;
case nvinfer1::DataType::kHALF:
case nvinfer1::DataType::kBF16:
size = sizeof( uint16_t );
break;
case nvinfer1::DataType::kFP8:
case nvinfer1::DataType::kINT8:
case nvinfer1::DataType::kBOOL:
case nvinfer1::DataType::kUINT8:
size = sizeof( uint8_t );
break;
default:
break;
}
return size;
}
}
bool ConvertONNXToTRT(
const Options& options,
const std::string& onnxModelPath,
std::string& trtFile,
const std::string prefix,
bool forceConvert
)
{
if ( !FileExists( onnxModelPath ) )
{
LOG_ERROR( "Cannot find ONNX mode at path: " + onnxModelPath );
return false;
}
// Find a hash string identifying the onnx we're going to convert to
// tensorrt, plus the gpu we're going to convert it for:
std::ifstream file(onnxModelPath, std::ios::binary);
std::string trtHash = picosha2::hash256_hex_string(
std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()
);
// hash in the cuda device name and precision::
trtHash = picosha2::hash256_hex_string(trtHash + GetCudaDeviceName( options.deviceID ));
trtHash = picosha2::hash256_hex_string(trtHash + std::to_string(int(options.precision)));
// name of the tensorrt we're going to write to (same directory as the ONNX model):
const auto filenamePos = onnxModelPath.find_last_of( '/' ) + 1;
const auto onnxDir = onnxModelPath.substr(0, filenamePos);
trtFile = onnxDir + prefix + onnxModelPath.substr( filenamePos, onnxModelPath.find_last_of( '.' ) - filenamePos );
trtFile += ".trt";
if (!forceConvert && FileExists( trtFile ) )
{
std::ifstream file(trtFile, std::ios::binary);
std::string hashInFile;
hashInFile.resize(trtHash.size());
file.read(const_cast<char*>(hashInFile.c_str()), trtHash.size());
std::cerr << "hash value in file " << hashInFile << std::endl;
std::cerr << "hash of onnx/gpu type " << trtHash << std::endl;
if(hashInFile == trtHash)
{
return true;
}
LOG_INFO( "TRT file " + trtFile + " is out of date and needs to be regenerated. This could take a while..." );
}
else
{
LOG_INFO( "TRT file " + trtFile + " not found. This could take a while to generate..." );
}
// Create engine builder
auto builder = std::unique_ptr<nvinfer1::IBuilder>( nvinfer1::createInferBuilder( s_logger ) );
if ( !builder )
{
LOG_ERROR( "Could not create builder." );
return false;
}
// Create network
const uint32_t explicitBatch = 1U << static_cast<uint32_t>( nvinfer1::NetworkDefinitionCreationFlag::kEXPLICIT_BATCH );
auto network = std::unique_ptr<nvinfer1::INetworkDefinition>( builder->createNetworkV2( explicitBatch ) );
if ( !network )
{
LOG_ERROR( "Could not create network." );
return false;
}
// Create a parser for reading the onnx file.
auto parser = std::unique_ptr<nvonnxparser::IParser>( nvonnxparser::createParser( *network, s_logger ) );
if ( !parser )
{
LOG_ERROR( "Could not create ONNX parser." );
return false;
}
// Read and parse the ONNX file
auto parsed = parser->parseFromFile( onnxModelPath.c_str(), 0 );
if ( !parsed )
{
LOG_ERROR( "Unable to parse the ONNX model at: " + onnxModelPath );
return false;
}
// Ensure that all the inputs have the same batch size
const int32_t inputCount = network->getNbInputs();
if ( inputCount < 1 )
{
LOG_ERROR( "Model has no inputs." );
return false;
}
// Set dynamic axis info for inputs:
auto profile = builder->createOptimizationProfile();
for ( int32_t i = 0; i < inputCount; ++i )
{
auto t = network->getInput( i );
const auto name = t->getName();
auto dims = t->getDimensions();
bool hasDynamicAxes = false;
auto dimsMin = dims;
auto dimsOpt = dims;
auto dimsMax = dims;
for ( int32_t n = 0; n < dims.nbDims; ++n )
{
if ( dims.d[n] == -1 )
{
auto axis_sizes = options.defaultSizes;
if (options.dynamic_axes_names.contains(t->getName()))
{
auto& dynamic_axis_names = options.dynamic_axes_names.at(t->getName());
if (!dynamic_axis_names.contains(n))
{
LOG_ERROR("Tensor " + std::string(t->getName()) + " has a dynamic axes, but its name isn't specified in options.input_dynamic_axes");
return false;
}
auto axis_name = dynamic_axis_names.at(n);
if (!options.dynamic_axes_sizes.contains(axis_name))
{
LOG_ERROR("Named axis " + axis_name + " has no entry in options.dynamic_axes_sizes");
return false;
}
axis_sizes = options.dynamic_axes_sizes.at(axis_name);
}
hasDynamicAxes = true;
dimsMin.d[n] = std::get<0>( axis_sizes );
dimsOpt.d[n] = std::get<1>( axis_sizes );
dimsMax.d[n] = std::get<2>( axis_sizes );
}
}
profile->setDimensions( name, nvinfer1::OptProfileSelector::kMIN, dimsMin );
profile->setDimensions( name, nvinfer1::OptProfileSelector::kOPT, dimsOpt );
profile->setDimensions( name, nvinfer1::OptProfileSelector::kMAX, dimsMax );
if ( t->isShapeTensor() )
{
if ( !options.shape_tensor_sizes.contains( name ) )
{
LOG_ERROR( "Tensor " + std::string( name ) + " controls the shape of an internal tensor and needs an entry in options.shape_tensor_sizes" );
return false;
}
auto &sizes = options.shape_tensor_sizes.at( name );
profile->setShapeValues( name, nvinfer1::OptProfileSelector::kMIN, std::get<0>( sizes ).data(), std::get<0>( sizes ).size());
profile->setShapeValues( name, nvinfer1::OptProfileSelector::kOPT, std::get<1>( sizes ).data(), std::get<1>( sizes ).size() );
profile->setShapeValues( name, nvinfer1::OptProfileSelector::kMAX, std::get<2>( sizes ).data(), std::get<2>( sizes ).size() );
}
}
auto config = std::unique_ptr<nvinfer1::IBuilderConfig>( builder->createBuilderConfig() );
if ( !config )
{
LOG_ERROR( "Could not create builder config." );
return false;
}
config->addOptimizationProfile( profile );
// Set the precision level
if ( options.precision == Precision::FP16 )
{
// Ensure the GPU supports FP16 inference
if ( !builder->platformHasFastFp16() )
{
LOG_ERROR( "GPU does not support FP16 precision." );
return false;
}
config->setFlag( nvinfer1::BuilderFlag::kFP16 );
}
// CUDA stream used for profiling by the builder.
cudaStream_t stream;
s_logger.log( cudaStreamCreate( &stream ) );
config->setProfileStream( stream );
// Build the engine
// If this call fails, it is suggested to increase the logger verbosity to kVERBOSE and try rebuilding the engine.
// Doing so will provide you with more information on why exactly it is failing.
auto serializedNetwork = builder->buildSerializedNetwork( *network, *config );
if ( !serializedNetwork )
{
s_logger.log( cudaStreamDestroy( stream ) );
LOG_ERROR( "Failed to build the engine!" );
return false;
}
// Write the engine to disk
std::ofstream outfile( trtFile, std::ofstream::binary );
// Write the onnx/gpu hash first:
outfile.write( trtHash.c_str(), trtHash.size() );
// Write the engine:
outfile.write( reinterpret_cast<const char*>( serializedNetwork->data() ), serializedNetwork->size() );
LOG_INFO( "Saved TRT file to " + trtFile );
s_logger.log( cudaStreamDestroy( stream ) );
return true;
}
class TRTInferenceEngine::Impl
{
public:
~Impl()
{
Destroy();
}
bool Initialize( const std::string& trtPath, int deviceIndex, const Options::AxisNames& axisNames )
{
Destroy();
// Read the serialized model from disk
std::ifstream file( trtPath, std::ios::binary | std::ios::ate );
std::streamsize size = file.tellg();
if (size < 0)
{
LOG_ERROR("Unable to read engine file!");
return false;
}
file.seekg( 0, std::ios::beg );
std::vector<char> buffer( size );
if ( !file.read( buffer.data(), size ) )
{
LOG_ERROR( "Unable to read engine file!" );
return false;
}
// Create runtime to deserialize the engine file.
m_runtime = std::unique_ptr<nvinfer1::IRuntime>( nvinfer1::createInferRuntime( s_logger ) );
if ( !m_runtime )
{
LOG_ERROR( "Unable to create a runtime inference model." );
return false;
}
// Set the device index
if ( cudaSetDevice( deviceIndex ) != cudaError_t::cudaSuccess )
{
int gpuCount;
cudaGetDeviceCount( &gpuCount );
LOG_ERROR( "Unable to set GPU device index to " + std::to_string( deviceIndex ) + ". Current device has " + std::to_string( gpuCount ) + " CUDA - capable GPU(s)." );
return false;
}
// Create an engine, a representation of the optimized model.
// skip the first 64 bytes when deserializing the engine file as they just contain a hash:
m_engine = std::unique_ptr<nvinfer1::ICudaEngine>( m_runtime->deserializeCudaEngine( buffer.data() + 64, buffer.size() - 64 ) );
if ( !m_engine )
{
LOG_ERROR("Unable to create engine.");
return false;
}
// The execution context contains all of the state associated with a particular invocation
m_context = std::unique_ptr<nvinfer1::IExecutionContext>( m_engine->createExecutionContext() );
if ( !m_context )
{
LOG_ERROR("Unable to create execution context.");
return false;
}
m_axis_names = axisNames;
const uint32_t tensorCount = m_engine->getNbIOTensors();
for (int32_t i = 0; i < tensorCount; ++i)
{
const auto tensorName = m_engine->getIOTensorName(i);
const auto tensorIOMode = m_engine->getTensorIOMode(tensorName);
m_dataTypeSizes[tensorName] = SizeOfTensorDataType(m_engine->getTensorDataType(tensorName));
}
return true;
}
bool InitInputs(const AxisSizes& axisSizes)
{
DestroyBuffers();
const uint32_t tensorCount = m_engine->getNbIOTensors();
for (int32_t i = 0; i < tensorCount; ++i)
{
const auto tensorName = m_engine->getIOTensorName(i);
const auto tensorIOMode = m_engine->getTensorIOMode(tensorName);
auto tensorShape = m_engine->getTensorShape(tensorName);
const auto tensorDataType = m_engine->getTensorDataType(tensorName);
for (int32_t n = 0; n < tensorShape.nbDims; ++n)
{
if (tensorShape.d[n] == -1)
{
int64_t batchSize = 1;
if (m_axis_names.contains(tensorName))
{
auto& dynamic_axis_names = m_axis_names.at(tensorName);
if (!dynamic_axis_names.contains(n))
{
LOG_ERROR("Tensor " + std::string(tensorName) + " has a dynamic axes, but its name isn't specified in axisNames");
return false;
}
auto axis_name = dynamic_axis_names.at(n);
if (!axisSizes.contains(axis_name))
{
LOG_ERROR("Named axis " + axis_name + " has no entry in axisSizes");
return false;
}
batchSize = axisSizes.at(axis_name);
}
else if (axisSizes.size() > 0)
{
batchSize = axisSizes.begin()->second;
}
tensorShape.d[n] = batchSize;
if (tensorIOMode == nvinfer1::TensorIOMode::kINPUT)
{
auto batchMin = m_engine->getProfileShape(tensorName, 0, nvinfer1::OptProfileSelector::kMIN).d[n];
auto batchMax = m_engine->getProfileShape(tensorName, 0, nvinfer1::OptProfileSelector::kMAX).d[n];
if (batchSize < batchMin || batchSize > batchMax)
{
LOG_ERROR("Batch size " + std::to_string(batchSize) + " is outside of possible range [ " + std::to_string(batchMin) + ", " + std::to_string(batchMax) + "].");
return false;
}
}
}
}
m_tensorShapes[tensorName] = tensorShape;
static const std::string dataTypeStr[] = { "FLOAT", "HALF", "INT8", "INT32", "BOOL", "UINT8", "FP8", "BF16", "INT64" };
static const std::string ioTypeStr[] = { "NONE", "INPUT", "OUTPUT" };
std::string tensorInfo = ioTypeStr[int(tensorIOMode)] + " Tensor: " + std::string(tensorName) + " [";
size_t elementCount = 1;
for (int32_t d = 0; d < tensorShape.nbDims; ++d)
{
if (d != 0)
tensorInfo += ", ";
tensorInfo += std::to_string(tensorShape.d[d]);
if (elementCount > INT64_MAX / tensorShape.d[d])
{
LOG_ERROR("Tensor too large: element count overflow");
return false;
}
elementCount *= tensorShape.d[d];
}
tensorInfo += "]. " + std::to_string(elementCount) + " elements of type " + dataTypeStr[int(tensorDataType)];
LOG_INFO(tensorInfo);
const auto sizeOfTensorData = SizeOfTensorDataType(tensorDataType);
if (sizeOfTensorData == -1)
{
LOG_ERROR("Unknown tensor data type " + std::to_string((int)tensorDataType) + ".");
return false;
}
void* cudaBuffer;
size_t size = elementCount * sizeOfTensorData;
auto err = cudaMalloc(&cudaBuffer, size);
s_logger.log(err);
if (err != cudaSuccess)
{
LOG_ERROR("Failed to allocate CUDA memory");
return false;
}
s_logger.log(cudaMemset(cudaBuffer, 0, size));
m_context->setTensorAddress(tensorName, cudaBuffer);
if (tensorIOMode == nvinfer1::TensorIOMode::kINPUT)
{
m_inputBuffers[tensorName] = cudaBuffer;
}
else
{
m_outputBuffers[tensorName] = cudaBuffer;
}
}
// Set input tensor shapes to specified batch size
for (int i = 0; i < tensorCount; ++i)
{
const auto tensorName = m_engine->getIOTensorName(i);
if (m_engine->getTensorIOMode(tensorName) != nvinfer1::TensorIOMode::kINPUT)
continue;
auto tensorShape = m_tensorShapes[tensorName];
m_context->setInputShape(tensorName, tensorShape);
}
return true;
}
void DestroyBuffers()
{
for (auto& kv : m_inputBuffers)
{
s_logger.log(cudaFree(kv.second));
}
for (auto& kv : m_outputBuffers)
{
s_logger.log(cudaFree(kv.second));
}
m_inputBuffers.clear();
m_outputBuffers.clear();
m_tensorShapes.clear();
}
void Destroy()
{
DestroyBuffers();
m_context.reset();
m_engine.reset();
m_runtime.reset();
}
bool ValidateTensorSize(const std::string& name, size_t byteCount) const
{
if (m_tensorShapes.empty())
{
LOG_ERROR("ValidateTensorSize: InitInputs has not been called.");
return false;
}
if (!m_tensorShapes.contains(name))
{
LOG_ERROR("ValidateTensorSize: Tensor " + name + " not found.");
return false;
}
size_t elementCount = 1;
const auto& tensorShape = m_tensorShapes.at(name);
for (int32_t d = 0; d < tensorShape.nbDims; ++d)
elementCount *= tensorShape.d[d];
const size_t expectedByteCount = elementCount * m_dataTypeSizes.at(name);
if (expectedByteCount != byteCount)
{
LOG_ERROR(
"Invalid data size for tensor [" + name + "] containing " + std::to_string(elementCount) + " elements. Expected " +
std::to_string(expectedByteCount) + " bytes, got " + std::to_string(byteCount) + " bytes instead.");
return false;
}
return true;
}
std::vector<std::string> GetInputBufferNames() const
{
std::vector<std::string> names;
names.reserve(m_inputBuffers.size());
for (const auto& pair : m_inputBuffers)
{
names.push_back(pair.first);
}
return names;
}
std::vector<std::string> GetOutputBufferNames() const
{
std::vector<std::string> names;
names.reserve(m_outputBuffers.size());
for (const auto& pair : m_outputBuffers)
{
names.push_back(pair.first);
}
return names;
}
void SetInputData(const std::string& name, const void* data, size_t byteCount)
{
if(!ValidateTensorSize(name, byteCount))
{
LOG_ERROR("SetInputData: Tensor " + name + " is not valid.");
return;
}
s_logger.log(cudaMemcpy(m_inputBuffers[name], data, byteCount, cudaMemcpyHostToDevice));
}
void SetInputDataAsync(const std::string& name, const void* data, size_t byteCount, cudaStream_t stream)
{
if(!ValidateTensorSize(name, byteCount))
{
LOG_ERROR("SetInputDataAsync: Tensor " + name + " is not valid.");
return;
}
s_logger.log(cudaMemcpyAsync(m_inputBuffers[name], data, byteCount, cudaMemcpyHostToDevice, stream));
}
void GetOutputData(const std::string& name, void* data, size_t byteCount)
{
if(!ValidateTensorSize(name, byteCount))
{
LOG_ERROR("GetOutputData: Tensor " + name + " is not valid.");
return;
}
s_logger.log(cudaMemcpy(data, m_outputBuffers[name], byteCount, cudaMemcpyDeviceToHost));
}
void GetOutputDataAsync(const std::string& name, void* data, size_t byteCount, cudaStream_t stream)
{
if(!ValidateTensorSize(name, byteCount))
{
LOG_ERROR("GetOutputDataAsync: Tensor " + name + " is not valid.");
return;
}
s_logger.log(cudaMemcpyAsync(data, m_outputBuffers[name], byteCount, cudaMemcpyDeviceToHost, stream));
}
bool Enqueue( cudaStream_t stream )
{
if (m_tensorShapes.empty())
{
LOG_ERROR("Enqueue: InitInputs has not been called.");
return false;
}
if (!m_context->allInputDimensionsSpecified())
{
LOG_ERROR("Inference engine inputs aren't defined.");
return false;
}
if(!m_context->enqueueV3(stream))
{
LOG_ERROR("Enqueue: Failed to enqueue.");
return false;
}
return true;
}
bool GetTensorShape(std::string name, std::vector<int64_t>& shape) const
{
if (m_tensorShapes.empty())
{
LOG_ERROR("ValidateTensorSize: InitInputs has not been called.");
return false;
}
if (!m_tensorShapes.contains(name))
{
LOG_ERROR("ValidateTensorSize: Tensor " + name + " not found.");
return false;
}
const auto& tensorShape = m_tensorShapes.at(name);
shape.clear();
for (int dim=0; dim < tensorShape.nbDims; ++dim)
{
shape.push_back(tensorShape.d[dim]);
}
return true;
}
nvinfer1::DataType GetTensorDataType(std::string name) const
{
auto tensorDataType = m_engine->getTensorDataType(name.c_str());
return tensorDataType;
}
std::unique_ptr<nvinfer1::IExecutionContext> m_context = nullptr;
std::unique_ptr<nvinfer1::ICudaEngine> m_engine = nullptr;
std::unique_ptr<nvinfer1::IRuntime> m_runtime = nullptr;
std::map<std::string, nvinfer1::Dims> m_tensorShapes;
std::map<std::string, void*> m_inputBuffers;
std::map<std::string, void*> m_outputBuffers;
std::map<std::string, int> m_dataTypeSizes;
Options::AxisNames m_axis_names;
};
TRTInferenceEngine::TRTInferenceEngine() : m_impl( new Impl )
{
}
TRTInferenceEngine::~TRTInferenceEngine()
{
}
bool TRTInferenceEngine::Initialize(const std::string& trtPath, int deviceIndex, const Options::AxisNames& axisNames)
{
return m_impl->Initialize(trtPath, deviceIndex, axisNames);
}
bool TRTInferenceEngine::InitInputs(const AxisSizes& axisSizes)
{
return m_impl->InitInputs(axisSizes);
}
void TRTInferenceEngine::Destroy()
{
m_impl->Destroy();
}
std::vector<std::string> TRTInferenceEngine::GetInputTensorNames() const
{
return m_impl->GetInputBufferNames();
}
std::vector<std::string> TRTInferenceEngine::GetOutputTensorNames() const
{
return m_impl->GetOutputBufferNames();
}
void TRTInferenceEngine::SetInputData(const std::string& name, const void* data, size_t byteCount)
{
m_impl->SetInputData(name, data, byteCount);
}
void TRTInferenceEngine::GetOutputData(const std::string& name, void* data, size_t byteCount)
{
m_impl->GetOutputData( name, data, byteCount);
}
void TRTInferenceEngine::SetInputDataAsync(const std::string& name, const void* data, size_t byteCount, cudaStream_t stream)
{
m_impl->SetInputDataAsync(name, data, byteCount, stream);
}
void TRTInferenceEngine::GetOutputDataAsync(const std::string& name, void* data, size_t byteCount, cudaStream_t stream)
{
m_impl->GetOutputDataAsync(name, data, byteCount, stream);
}
bool TRTInferenceEngine::Enqueue(cudaStream_t stream)
{
return m_impl->Enqueue(stream);
}
bool TRTInferenceEngine::GetTensorShape(std::string name, std::vector<int64_t>& shape) const
{
return m_impl->GetTensorShape(name, shape);
}
DataType TRTInferenceEngine::GetTensorDataType(std::string name) const
{
auto dataType = m_impl->GetTensorDataType(name);
switch (dataType)
{
case nvinfer1::DataType::kFLOAT:
return DataType::FLOAT;
case nvinfer1::DataType::kHALF:
return DataType::HALF;
case nvinfer1::DataType::kINT8:
return DataType::INT8;
case nvinfer1::DataType::kINT32:
return DataType::INT32;
case nvinfer1::DataType::kBOOL:
return DataType::BOOL;
case nvinfer1::DataType::kUINT8:
return DataType::UINT8;
case nvinfer1::DataType::kINT64:
return DataType::INT64;
default:
LOG_ERROR("ValidateTensorSize: Tensor " + name + " not found.");
return DataType::UNKNOWN;
}
}