개인적으로 읽고 쓰는 공부용 리뷰입니다.

틀린 점이 있을 수도 있으니 감안하고 읽어주세요. 피드백은 댓글로 부탁드립니다.

[TensorRT] 1. Build tensorrt engine (tensorRT 7.2.3)


Serializie는 나중에 재사용을 위해 저장하기위한 포맷으로 바꾸는 것을 의미한다. inference에 사용하기 위해서는 그냥 deserializie한 뒤 쓰면 된다. 보통 빌드과정이 시간을 소요하기 때문에 매번 빌드하는 것을 피하기 위해 이 과정을 한다.

// code for serializing ICudaEngine
IHostMemory *serializedModel = engine->serialize();
// store model to disk
// <…>
 serializedModel->destroy();

-

Deserialize code, The final argument is a plugin layer factory for applications using custom layers. For more information, see [Extending TensorRT With Custom Layers]
별거아니고, 마지막에 nullptr는 iplugin for custom layer인데 없다면 그냥 nullptr넣으면 된다.

// code for deserializing ICudaEngine
IRuntime* runtime = createInferRuntime(gLogger);
ICudaEngine* engine = runtime->deserializeCudaEngine(modelData, modelSize, nullptr)

주의 할점은 trt version, gpu, platform을 항시 잘 체크해야한다.

  • Serialized engines are not portable across platforms or TensorRT versions.
  • Engines are specific to the exact GPU model they were built on.

 

위에는 tensorrt reference의 코드인데 처음에보고 어떻게 더 추가해야할지 몰라서 막막했다.
아래는 실제로 내가 사용하는 serializing & save 코드다.

여기서 engine_ 은 빌드가 성공적으로 된 ICudaEngine이다.

bool saveEngine( std::string &fileName ) const 
{
    std::ofstream engineFile( fileName, std::ios::binary );
    if ( !engineFile ) {
        gLogFatal << "Cannot open engine file : " << fileName << std::endl;
        return false;
    }

    if ( engine_ == nullptr ) {
        gLogError << "Engine is not defined" << std::endl;
        return false;
    }
    nvinfer1::IHostMemory *serializedEngine{engine_->serialize()};
    if ( serializedEngine == nullptr ) {
        gLogError << "Engine serialization failed" << std::endl;
        return false;
    }

    engineFile.write( static_cast<char *>( serializedEngine->data() ),
                      serializedEngine->size() );
    if ( engineFile.fail() ) {
        gLogError << "Failed to save Engine." << std::endl;
        return false;
    }
    std::cout << "Successfully save to : " << fileName << std::endl;
    return true;
}

 

다음은 저장된 ICudaEngine을 load후 다시 deserializing하는 코드다. 

bool Load( const std::string &fileName ) {
    std::ifstream engineFile( fileName, std::ios::binary );
    if ( !engineFile ) {
        std::cout << "can not open file : " << fileName << std::endl;
        return false;
    }
    engineFile.seekg( 0, engineFile.end );
    auto fsize = engineFile.tellg();
    engineFile.seekg( 0, engineFile.beg );

    std::vector<char> engineData( fsize );
    engineFile.read( engineData.data(), fsize );

    Load( engineData.data(), ( long int )fsize );
    return true;
}

bool Load( const void *engineData, const long int fsize ) {
    nvinfer1::IRuntime *runtime = nvinfer1::createInferRuntime( gLogger.getTRTLogger() );
    engine_ = runtime->deserializeCudaEngine( engineData, fsize, nullptr );
    // if u want DLA core setting, then u shoud write code here
    runtime->destroy();
    return true;
}

두가지 방법으로 load할수있어서 오버로딩해놨다.

끝 

'Deep Learning > tensorrt' 카테고리의 다른 글

[TensorRT] 1. Build tensorrt engine (tensorRT 7.2.3)  (6) 2021.03.24

개인적으로 읽고 쓰는 공부용 리뷰입니다.

틀린 점이 있을 수도 있으니 감안하고 읽어주세요. 피드백은 댓글로 부탁드립니다.

본래는 0. Install tensorrt 부터 하고싶었는데 아.. 설치과정은 굳이 리뷰하기 귀찮다
그리고 어차피 이걸 누가 주의깊게 읽을거라고 생각안한다.
원래 논문도 매주 하나씩 리뷰하려고 했는데 이게 상당히 시간을 요하는 작업이라 이게 이후에 이어질지는 사실 나도 잘 모르겠다.
아무튼 이번에 쓰는 포스트는 어떤 과정을 통해 내가 학습한 모델이 tensorrt 모델로 변환이 되는지 정리하려고 한다.
아래는 레퍼런스다.
https://docs.nvidia.com/deeplearning/tensorrt/developer-guide/#create_network_c
https://docs.nvidia.com/deeplearning/tensorrt/api/c_api/index.html
eehoeskrap.tistory.com/
레퍼런스를 먼저 말하는 이유는 위에 링크를 안보고는 바로 이해하기 힘들 것 같아서이다. 세번째에 링크된 꾸준희님 블로그는 관련된 포스트가 많다. 그리고 내가 쓰는 글에 비해 훨씬 나을 것 이다.
아무튼, nvidia에서는 빠른 속도를 위해 다양한 프레임워크에서 학습된 custom model들을 gpu에 최적화 시켜주는 라이브러리를 제공하는데 이게 tensorrt다. 결론만 말하자면, tensorrt(이하 trt)의 목적은 빠른 inference 속도를 위한 것이다.
이 과정이 처음에 접하기에는 굉장히 복잡하다.
다시 본 내용으로 와서 나는 pytorch, tensorflow 또는 caffe 등 다양한 framework를 통해 딥러닝 모델을 학습을 시킨다. 그리고 nvidia에서 만든 이 tensorrt는 이러한 모델을 parsing하는 기능을 제공하며, gpu에 최적화시켜 보다 빠른 추론속도를 가능케해주는 라이브러리다. 좋다. 단순히 trtexec를 써도되지만, 좀더 깊게 내가 직접 빌드할때가 분명히 있을것 이다. 그리고 100% 나처럼 무조건 다 직접 이해하고 짜야만 직성이 풀리는 사람도 있을 것이다. 삽질을 좋아하는 사람.
그리고 더좋은건 c++ 과 python 두 언어에 대한 api를 제공한다. pytorch를 사용하는 입장에서, 당연히 python api를 사용하고 싶지만, 그럴여건이 되지 않을 때도 있다. 이 포스트는 c++ 뉴비인(나 포함) 사람들에게 혹시나 도움이 될까 쓰는 개인 저장용 포스트다.(참고로 python api는 window를 지원하지않는다)
이 포스트를 보기전에 상단의 레퍼런스 첫번째 링크 적어도 2.1은 정독하는걸 매우 추천한다.
trt에서 custom model을 trt engine으로 빌드하는 과정은 다음과 같은 과정을 거친다. 이 과정의 목적은 tensorrt에서 사용하는 ICudaEngine을 만드는 것이다. 그리고 이 아래로 bold 처리된 것은 모두 클래스다.
1. create IBuilder by using createInferBuilder with Logger.(logger는 trt sample code에 있는거 사용 추천)
2. create IBuilderConfig by using IBuilder::createBuilderConfig().
3. define IBuilderConfig options. ex) fp16, int8, dla etc..
4. create INetworkDefinition by using IBuilder::createNetworkV2().
5. create proper IParser for defining INetworkDefinition.
6. define INetworkDefinition by using IParser::parse
7. call IBuilder::buildEngineWithConfig() with pre-defined INetworkDefinition & IBuilderConfig
8. save or run ICudaEngine with IExecutionContext
대충 흐름이 파악됐다면, 이제 천천히 한단계씩 보면 된다.

1. create IBuilder by using createInferBuilder with Logger

IBuilderICudaEngine을 만들기 위한 이런저것들을 정의할 클래스다.
auto builder = UniquePtr<IBuilder>(createInferBuilder(gLogger.getTRTLogger()));
대충 이런식으로 만든다 UniquePtr은 내가만든 포인터 형식인데 그냥 아래처럼 쓰면된다. 효율적인 포인터 사용을 위해 nvidia코드를 베낀것이다. 아무튼 여기까진 쉽다.
IBuilder* builder = nvinfer1::createInferBuilder(gLogger.getTRTLogger());
레퍼런스에 있는 예시처럼 (아래) 간단하게 만들어도 되지만, 그냥 sample코드에있는 logger.h, logging.h, 등등을 복붙해서 사용하는 것을 추천한다.
class Logger : public ILogger
{
void log(Severity severity, const char* msg) override
{
// suppress info-level messages
if (severity != Severity::kINFO)
std::cout << msg << std::endl;
}
} gLogger;

2. create IBuilderConfig by using IBuilder::createBuilderConfig().

auto config = UniquePtr<IBuilderConfig>( builder->createBuilderConfig() );
IBuilder를 만들었으면 위처럼 builderconfig를 만든다. 이글을 볼때쯤엔 다알겠지만 trt engine을 빌드할때, fp16(half), int8 등의 옵션을 쓰며 build할 수 있다. 이러한 옵션을 IBuilderConfig가 정의한다. 자세한건 다음에 블로깅될 포스트 또는 레퍼런스를 참고하면된다. 참고로 dla를 쓸지안쓸지도 이 클래스로 정의한다.

3. define IBuilderConfig options. ex) fp16, int8, dla etc..

2번에서 에서 얘기했다

4. create INetworkDefinition by using IBuilder::createNetworkV2().

auto network = UniquePtr<INetworkDefinition>(builder->createNetworkV2(explicitBatch));
위처럼 만든다. 뭐냐면 그냥 내 커스텀 모델의 네트워크 정보들을 담는 변수다. 이 변수를 만드는 과정이다.

5. create proper IParser for defining INetworkDefinition.

INetworkDefinition을 정의하기위해선 적절한 parser를 사용해야한다. 당연히 trt자체로 parser를 제공한다. 세가지 parser를 제공한다. caffer, uff, onnx. IParser는 다음처럼 만들면 된다. 간단하다.
auto parser = nvonnxparser::createParser(*network, gLogger);
auto parser = nvcaffeparser1::createCaffeParser();
auto parser = nvuffparser::createUffParser();

6. define INetworkDefinition by using IParser::parse

IParser를 만들었으면 IParser::parse()를 통해 내 INetworkDefinition을 정의하면된다. IParser::parse()를 이용하면 된다.
아마 직접 코드를 짜서 빌드를한다면 99.9%여기에서 에러가 날것이다. 지원하지 않는 레이어도 많고 연산도 많다. 해결 방법은 두가지다. 지원하는 레이어로만 구성해서 원하는 결과가 나오도록 모델을 구성하던가, 직접 커스텀 레이어를 짜던가. 후자는 나중에 포스팅이 될것이다 언젠간.

7. call IBuilder::buildEngineWithConfig() with pre-defined INetworkDefinition & IBuilderConfig
아무튼 이제 INetworkDefinitionIBuilderConfig를 정의했을것이다. 그럼 이제 빌드만 하면 된다. 아래처럼 IBuilder::createBuilderConfig()를 호출하면된다.
mEngine = shared_ptr<ICudaEngine>(
builder->buildEngineWithConfig( *network, *config ), InferDeleter() );
return 값이 ICudaEngine이다.




끝이다.

이제 이 결과 값으로 inference를 하면 된다. 저장을하던가. 근데 저장하려면 serealizing이 필요하다. 나중에 포스팅하겠다.
그리고 중요한건 이 아웃풋 엔진은 gpu에 최적화과 되어있는 상태다. 즉 내가 지금 이 과정까지 gtx 1080 에서 했다면 gtx titan에서 못쓴다. 가능하긴 하지만 비추천한다. 관련 officail 링크다. docs.nvidia.com/deeplearning/tensorrt/developer-guide/index.html#troubleshooting

+ Recent posts