本文介绍TensorRT10.3.0的编程基础。
1、创建引擎
1、创建log日志
1 2 3 4 5 6 7 8 9 10 11 12
| class Logger : public ILogger { public: void log(Severity severity, const char *msg) noexcept override { if (severity <= Severity::kWARNING) { std::cout << msg << std::endl; } } };
|
2、创建推理构建器
1
| IBuilder *builder = createInferBuilder(logger);
|
3、构建网络
1 2
| uint32_t flag = 0; INetworkDefinition *network = builder->createNetworkV2(flag);
|
4、加载onnx模型并解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| IParser *parser = createParser(*network, logger);
const std::string onnxModelPath = "model.onnx"; std::ifstream modelFile(onnxModelPath, std::ios::binary); if (!modelFile) { std::cerr << "无法打开模型文件:" << onnxModelPath << std::endl; return -1; } modelFile.seekg(0, std::ios::end); size_t modelSize = modelFile.tellg(); modelFile.seekg(0, std::ios::beg); std::vector<char> modelData(modelSize); modelFile.read(modelData.data(), modelSize); modelFile.close();
if (!parser->parse(modelData.data(), modelSize)) { std::cerr << "解析 ONNX 模型失败:" << std::endl; for (int i = 0; i < parser->getNbErrors(); ++i) { std::cerr << parser->getError(i)->desc() << std::endl; } return -1; }
|
5、构建配置并且序列化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| IBuilderConfig *config = builder->createBuilderConfig();
IHostMemory *serializedModel = builder->buildSerializedNetwork(*network, *config); if (!serializedModel) { std::cerr << "构建序列化网络失败" << std::endl; return -1; }
const std::string engineFilePath = "model.engine"; std::ofstream engineFile(engineFilePath, std::ios::binary); engineFile.write(reinterpret_cast<const char *>(serializedModel->data()), serializedModel->size()); engineFile.close();
delete parser; delete network; delete config; delete builder;
std::cout << "引擎构建并保存成功:" << engineFilePath << std::endl; return 0;
|
6、完整基础代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| #include <NvInfer.h> #include <NvOnnxParser.h> #include <iostream> #include <fstream> #include <vector>
using namespace nvinfer1; using namespace nvonnxparser;
class Logger : public ILogger { public: void log(Severity severity, const char *msg) noexcept override { if (severity <= Severity::kWARNING) { std::cout << msg << std::endl; } } };
int main() { Logger logger;
IBuilder *builder = createInferBuilder(logger);
uint32_t flag = 0; INetworkDefinition *network = builder->createNetworkV2(flag);
IParser *parser = createParser(*network, logger);
const std::string onnxModelPath = "model.onnx"; std::ifstream modelFile(onnxModelPath, std::ios::binary); if (!modelFile) { std::cerr << "无法打开模型文件:" << onnxModelPath << std::endl; return -1; } modelFile.seekg(0, std::ios::end); size_t modelSize = modelFile.tellg(); modelFile.seekg(0, std::ios::beg); std::vector<char> modelData(modelSize); modelFile.read(modelData.data(), modelSize); modelFile.close();
if (!parser->parse(modelData.data(), modelSize)) { std::cerr << "解析 ONNX 模型失败:" << std::endl; for (int i = 0; i < parser->getNbErrors(); ++i) { std::cerr << parser->getError(i)->desc() << std::endl; } return -1; }
IBuilderConfig *config = builder->createBuilderConfig();
IHostMemory *serializedModel = builder->buildSerializedNetwork(*network, *config); if (!serializedModel) { std::cerr << "构建序列化网络失败" << std::endl; return -1; }
const std::string engineFilePath = "model.engine"; std::ofstream engineFile(engineFilePath, std::ios::binary); engineFile.write(reinterpret_cast<const char *>(serializedModel->data()), serializedModel->size()); engineFile.close();
delete parser; delete network; delete config; delete builder;
std::cout << "引擎构建并保存成功:" << engineFilePath << std::endl; return 0; }
|
当然也可以如下自定义网络不用onnx:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| INetworkDefinition* network = builder->createNetworkV2(0);
auto input = network->addInput("input", DataType::kFLOAT, Dims3(1, 28, 28));
auto conv = network->addConvolution(*input, 32, DimsHW(3, 3), weightTensor, biasTensor); conv->setStride(DimsHW(1, 1));
auto relu = network->addActivation(*conv->getOutput(0), ActivationType::kRELU);
auto pool = network->addPooling(*relu->getOutput(0), PoolingType::kMAX, DimsHW(2, 2)); pool->setStride(DimsHW(2, 2));
auto output = network->addOutput(*pool->getOutput(0));
IBuilderConfig* config = builder->createBuilderConfig(); ICudaEngine* engine = builder->buildCudaEngine(*network);
|
7、序列化的意义
TensorRT 序列化的作用和意义主要体现在以下几个方面:
1. 提高推理效率
TensorRT 序列化的主要作用之一是将经过优化的网络模型保存为一个二进制文件(通常是 .engine
文件)。这个文件包含了网络的结构、权重以及与推理相关的优化信息。在推理时,TensorRT 可以直接加载这个序列化的引擎文件,而不需要每次都重新解析原始的 ONNX 模型或进行优化。这样可以显著减少推理的初始化时间,并提高推理的效率。
2. 优化推理性能
通过序列化,TensorRT 会在创建引擎的过程中进行一系列的优化,例如:
层融合:将多个算子融合成一个更高效的算子,减少计算量。
精度降低:通过使用 FP16 或 INT8 等低精度进行推理,以提高速度并减少内存使用。
内存管理:优化内存的使用方式,使得内存的占用最小化,尤其是在 GPU 上,能够有效利用显存。
动态批量大小支持:序列化后的引擎通常支持动态批量大小,这意味着引擎可以根据实际输入的批量大小自动调整,从而避免了不必要的内存浪费。
3. 便于模型部署
通过序列化,TensorRT 将网络模型变成了一个标准化的文件(.engine
),这个文件可以被部署到不同的环境中。例如:
序列化使得模型从训练到部署的过程变得更加高效和简单。
4. 节省存储和计算资源
5. 与硬件加速紧密结合
TensorRT 序列化后的引擎文件通常会根据目标硬件(如 GPU 类型)进行特定优化。例如:
6. 支持多种精度
TensorRT 序列化时,可以指定精度模式(如 FP32、FP16 或 INT8)。通过在序列化过程中将精度降低为较低精度(如 FP16 或 INT8),可以大幅提高推理速度,并减少显存占用。这些低精度模式对于推理任务非常重要,尤其是在性能和功耗要求严格的环境中(如边缘设备)。
7. 简化部署和集成
使用 TensorRT 序列化的引擎文件,用户不需要重新编译或重新构建推理代码。只需要将引擎文件和相应的加载代码部署到目标设备上,程序就可以直接加载并进行推理。这种方式简化了部署过程,并减少了配置和测试的复杂性。
2、使用引擎推理
1、加载引擎并且反序列化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| ICudaEngine *loadEngine(const std::string &engineFilePath, Logger &logger) { std::ifstream engineFile(engineFilePath, std::ios::binary); if (!engineFile) { std::cerr << "无法打开引擎文件:" << engineFilePath << std::endl; return nullptr; }
std::vector<char> engineData((std::istreambuf_iterator<char>(engineFile)), std::istreambuf_iterator<char>()); engineFile.close();
IRuntime *runtime = createInferRuntime(logger); if (!runtime) { std::cerr << "无法创建推理运行时" << std::endl; return nullptr; }
ICudaEngine *engine = runtime->deserializeCudaEngine(engineData.data(), engineData.size()); if (!engine) { std::cerr << "无法反序列化引擎" << std::endl; return nullptr; }
return engine; }
|
在使用 TensorRT 序列化引擎时,反序列化的过程是将保存为 .engine
文件的序列化引擎加载到内存中,并准备好用于推理。反序列化过程的主要目的是将之前序列化并保存的引擎文件转换回一个可以执行推理的对象。
2、创建上下文进行推理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| void doInference(ICudaEngine *engine, void *inputData, void *outputData) { IExecutionContext *context = engine->createExecutionContext(); if (!context) { std::cerr << "无法创建执行上下文" << std::endl; return; }
void *buffers[2]; buffers[0] = inputData; buffers[1] = outputData;
context->executeV2(buffers);
delete context; }
|
3、使用实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| int main() { Logger logger;
const std::string engineFilePath = "model.engine"; ICudaEngine *engine = loadEngine(engineFilePath, logger); if (!engine) { std::cerr << "加载引擎失败" << std::endl; return -1; }
int inputSize = 1 * 3 * 224 * 224; int outputSize = 1 * 1000;
float *inputData = new float[inputSize]; float *outputData = new float[outputSize];
void *inputDevice = nullptr; void *outputDevice = nullptr; cudaMalloc(&inputDevice, inputSize * sizeof(float)); cudaMalloc(&outputDevice, outputSize * sizeof(float));
cudaMemcpy(inputDevice, inputData, inputSize * sizeof(float), cudaMemcpyHostToDevice);
doInference(engine, inputDevice, outputDevice);
cudaMemcpy(outputData, outputDevice, outputSize * sizeof(float), cudaMemcpyDeviceToHost);
for (int i = 0; i < 5; i++) { std::cout << "Output[" << i << "] = " << outputData[i] << std::endl; }
delete[] inputData; delete[] outputData; cudaFree(inputDevice); cudaFree(outputDevice);
delete engine;
return 0; }
|
3、关于tensorRT的优化
TensorRT 的高性能推理优化方法主要体现在以下几个方面:
- **层融合 (Layer Fusion)**:
- **内核自动调优 (Kernel Auto-Tuning)**:
- **动态张量内存管理 (Dynamic Tensor Memory Management)**:
这些优化使得 TensorRT 在推理任务中能够显著提高速度,尤其在大规模模型部署时,能够充分发挥硬件的性能优势。