深度学习模型部署(十一)TensorRT写Plugin

什么是plugin & 有什么用?

TensorRT的一种机制,以.so的形式插入到网络中实现某些算子。
作用:

  • 实现TensorRT不支持的层
  • 替换性能不好的层
  • 手动进行图优化算子融合

写Plugin就是自己写算子的CUDA kernel实现。
Plugin与其他layer之间无法自动进行算子融合,可能会在plugin前后加入reformating节点,增加开销。
建议先进行原生layer组合保证计算正确性,再尝试官方自带的Plugin是否满足要求,都不行再自己写plugin。

创建Plugin工作流程:

实现一个算子,对输入的张量每个元素加上一个常量

  • 继承 IPluginV2DynamicExt 类实现一个Plugin 类
  • 继承 IPluginCreator 类实现一个 PluginCreator 类
  • 实现用于计算的 CUDA C++ kernel
  • 将 Plugin 编译为 .so 保存
  • 在 TenorRT 中加载和使用 Plugin

实现Plugin类

继承IPluginV2DynamicExt类

Plugin有V1和V2两个版本,V1已经弃用,V2分为:IPluginV2,IPluginV2Ext,IPluginV2IOExt,IPluginV2DynamicExt四种,第三种第四种最常用
在这里插入图片描述

class AddScalarPlugin : public IPluginV2DynamicExt // 定义AddScalarPlugin类,继承IPluginV2DynamicExt类
{
private:
    const std::string name_; //算子名称
    std::string       namespace_; //算子所属的域
    struct
    {
        float scalar;
    } m_;

public:
    AddScalarPlugin() = delete; //禁止默认构造函数
    AddScalarPlugin(const std::string &name, float scalar);
    AddScalarPlugin(const std::string &name, const void *buffer, size_t length); //构造函数
    ~AddScalarPlugin();

    // Method inherited from IPluginV2
    const char *getPluginType() const noexcept override; //获取插件类型,noexcept表示该函数不会抛出异常,override表示该函数是虚函数
    const char *getPluginVersion() const noexcept override; //获取插件版本
    int32_t     getNbOutputs() const noexcept override; //获取输出张量的数量
    int32_t     initialize() noexcept override; //初始化插件
    void        terminate() noexcept override; //终止插件,释放资源
    size_t      getSerializationSize() const noexcept override; //获取序列化后的大小
    void        serialize(void *buffer) const noexcept override; //序列化
    void        destroy() noexcept override; //销毁插件,当context或engine被销毁时,插件也会被销毁
    void        setPluginNamespace(const char *pluginNamespace) noexcept override; //设置插件的命名空间
    const char *getPluginNamespace() const noexcept override; //获取插件的命名空间
    //当我们的模型来自onnx的时候,命名空间,版本等信息会被保存在onnx模型中,这个函数就是用来获取这些信息的
    //一般不用我们自己设置,而是由onnx模型中的信息来设置
    //如果这些信息设置不对,会导致onnxparser解析模型的时候出错,无法识别插件

    // Method inherited from IPluginV2Ext
    DataType getOutputDataType(int32_t index, DataType const *inputTypes, int32_t nbInputs) const noexcept override;
    void     attachToContext(cudnnContext *contextCudnn, cublasContext *contextCublas, IGpuAllocator *gpuAllocator) noexcept override;
    void     detachFromContext() noexcept override;

    // Method inherited from IPluginV2DynamicExt
    IPluginV2DynamicExt *clone() const noexcept override;
    DimsExprs            getOutputDimensions(int32_t outputIndex, const DimsExprs *inputs, int32_t nbInputs, IExprBuilder &exprBuilder) noexcept override;
    // getOutputDimensions,向TensorRT报告输出张量的形状,outputIndex是指输出张量的索引
    bool                 supportsFormatCombination(int32_t pos, const PluginTensorDesc *inOut, int32_t nbInputs, int32_t nbOutputs) noexcept override;
    // supportsFormatCombination,检查输入和输出张量的格式是否支持,pos是指输入张量的索引,inOut是指输入和输出张量的描述符, nbInputs是指输入张量的数量,nbOutputs是指输出张量的数量
    // 尽量多的支持格式组合,以便TensorRT可以选择最佳的格式组合
    void                 configurePlugin(const DynamicPluginTensorDesc *in, int32_t nbInputs, const DynamicPluginTensorDesc *out, int32_t nbOutputs) noexcept override;
    // configurePlugin,配置插件,in是指输入张量的描述符,nbInputs是指输入张量的数量,out是指输出张量的描述符,nbOutputs是指输出张量的数量
    // 在推理期前调用该函数,用于将插件中的动态维度转换为静态维度
    size_t               getWorkspaceSize(const PluginTensorDesc *inputs, int32_t nbInputs, const PluginTensorDesc *outputs, int32_t nbOutputs) const noexcept override;
    // getWorkspaceSize,获取插件所需的工作空间大小,inputs是指输入张量的描述符,nbInputs是指输入张量的数量,outputs是指输出张量的描述符,nbOutputs是指输出张量的数量
    // 在推理期前调用该函数,用于计算插件所需的工作空间大小,向TensorRT报告工作空间的大小
    int32_t              enqueue(const PluginTensorDesc *inputDesc, const PluginTensorDesc *outputDesc, const void *const *inputs, void *const *outputs, void *workspace, cudaStream_t stream) noexcept override;
    // enqueue,执行插件的推理,inputDesc是指输入张量的描述符,outputDesc是指输出张量的描述符,inputs是指输入张量的数据,outputs是指输出张量的数据,workspace是指工作空间,stream是指CUDA流
    // 在推理期间调用该函数,用于执行插件的推理。不要在enqueue中调用cudaMalloc或cudaFree等CUDA API,会造成性能下降
    // 原因我猜是因为前面getworkspaceSize已经分配了空间,如果这里再进行分配,会使之前针对内存分配做的优化失效
protected:
    // To prevent compiler warnings,使用using声明,将基类的成员函数引入到子类中,避免编译器警告
    using nvinfer1::IPluginV2::enqueue;
    using nvinfer1::IPluginV2::getOutputDimensions;
    using nvinfer1::IPluginV2::getWorkspaceSize;
    using nvinfer1::IPluginV2Ext::configurePlugin;
};

实现PluginCreator类

继承IPluginCreator类


class AddScalarPluginCreator : public IPluginCreator 
// 定义一个AddScalarPluginCreator类,继承于IPluginCreator,PluginCreator是一个工厂类,用于创建Plugin
{
private:
    static PluginFieldCollection    fc_;
    static std::vector<PluginField> attr_;
    std::string                     namespace_;

public:
    AddScalarPluginCreator();
    ~AddScalarPluginCreator();
    const char                  *getPluginName() const noexcept override;
    const char                  *getPluginVersion() const noexcept override;
    const PluginFieldCollection *getFieldNames() noexcept override;
    IPluginV2DynamicExt         *createPlugin(const char *name, const PluginFieldCollection *fc) noexcept override;
    // 接受一个插件名称和插件属性集合,返回一个新的插件实例
    IPluginV2DynamicExt         *deserializePlugin(const char *name, const void *serialData, size_t serialLength) noexcept override;
    // 接受一个插件名称和序列化数据,返回一个新的插件实例
    void                         setPluginNamespace(const char *pluginNamespace) noexcept override;
    // 设置插件的命名空间
    const char                  *getPluginNamespace() const noexcept override;
    // 获取插件的命名空间
};

实现kernel函数

// kernel for GPU
__global__ void addScalarKernel(const float *input, float *output, const float scalar, const int nElement)
// cuda中global关键字修饰函数表示该函数必须由CPU调用,GPU运行
{
    const int index = blockIdx.x * blockDim.x + threadIdx.x;
    //cuda中kernel函数内置变量blockIdx表示目前执行该kernel的block信息,threadIdx表示执行该kernel的thread信息
    if (index >= nElement) // 如果越界就返回,否则会出现内存访问错误
        return; //cuda中kernel不允许返回值,但是return可以用来提前结束函数

    float _1      = input[index];
    float _2      = _1 + scalar;
    output[index] = _2;
}

int32_t AddScalarPlugin::enqueue(const PluginTensorDesc *inputDesc, const PluginTensorDesc *outputDesc, const void *const *inputs, void *const *outputs, void *workspace, cudaStream_t stream) noexcept
{
    WHERE_AM_I();
    int nElement = 1;
    for (int i = 0; i < inputDesc[0].dims.nbDims; ++i)
    {
        nElement *= inputDesc[0].dims.d[i];
    }
    dim3 grid(CEIL_DIVIDE(nElement, 256), 1, 1), block(256, 1, 1);
    addScalarKernel<<<grid, block, 0, stream>>>(reinterpret_cast<const float *>(inputs[0]), reinterpret_cast<float *>(outputs[0]), m_.scalar, nElement);
    return 0;
}

编译

include ../include/Makefile.inc

SOURCE_CU   = $(shell find . -name '*.cu' 2>/dev/null)
SOURCE_PY   = $(shell find . -name '*.py' 2>/dev/null)
OBJ         = $(shell find . -name *.o 2>/dev/null)
DEP         = $(OBJ:.o=.d)
TARGET_SO   = $(SOURCE_CU:.cu=.so)

-include $(DEP)

all: $(TARGET_SO)

%.so: %.o
	$(NVCC) $(SOFLAG) $(LDFLAG) -o $@ $+
# nvcc是指定编译器,-shared是指定生成动态链接库,-o是指定生成的动态链接库的名字,$+是指定生成动态链接库的目标文件


%.o: %.cu
	$(NVCC) $(CUFLAG) $(INCLUDE) -M -MT $@ -o $(@:.o=.d) $<
	$(NVCC) $(CUFLAG) $(INCLUDE) -o $@ -c $<

.PHONY: test
# PHONY是一个伪目标,它表示不管是否存在这个文件,只要执行这个目标,就会执行后面的命令
# 伪目标是指不生成任何文件,只是执行一些特定的命令
test:
	make clean
	make
	python3 $(SOURCE_PY)

.PHONY: clean
clean:
	rm -rf ./*.d ./*.o ./*.so ./*.exe ./*.plan

加载使用

import ctypes
import os

import numpy as np
import tensorrt as trt
from cuda import cudart

soFile = "./AddScalarPlugin.so"
logger = trt.Logger(trt.Logger.ERROR)
trt.init_libnvinfer_plugins(logger, '')
# trt.init_libnvinfer_plugins函数的作用是初始化TensorRT库中的插件,其中的两个参数分别是日志级别和插件库的路径。
ctypes.cdll.LoadLibrary(soFile)
# ctypes.cdll.LoadLibrary函数的作用是加载指定的动态链接库,其中的参数是动态链接库的路径。
构建期
  • TensorRT 向 Plugin 传输参数和权重
  • Plugin 向 TensorRT 报告其输入输出张量信息,包括数量、形状(Shape)、数据类型(DataType)和数据排布(Layout)组合
  • Plugin 向 TensorRT 报告其需要的 workspace 大小
  • TensorRT 尝试各种允许的组合,选择性能最佳的输入输出组合(可能在 Plugin 前后插入 reformat 节点)
  • Plugin 不参与层 fusing
def getAddScalarPlugin(scalar):
    for c in trt.get_plugin_registry().plugin_creator_list:
        #print(c.name)
        if c.name == "AddScalar":
            parameterList = []
            parameterList.append(trt.PluginField("scalar", np.float32(scalar), trt.PluginFieldType.FLOAT32))
            # PluginField类的作用是定义插件的属性,其中的三个参数分别是属性的名称、属性的值和属性的数据类型。
            return c.create_plugin(c.name, trt.PluginFieldCollection(parameterList))
            # create_plugin函数的作用是创建一个插件,其中的两个参数分别是插件的名称和插件的属性集合。
    return None

builder = trt.Builder(logger)
network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))
profile = builder.create_optimization_profile()
config = builder.create_builder_config()

inputT0 = network.add_input("inputT0", trt.float32, [-1 for i in shape])
profile.set_shape(inputT0.name, [1 for i in shape], [8 for i in shape], [32 for i in shape])
config.add_optimization_profile(profile)

pluginLayer = network.add_plugin_v2([inputT0], getAddScalarPlugin(scalar))
# add_plugin_v2函数的作用是向网络中添加一个插件层,其中的两个参数分别是输入张量列表和插件。
network.mark_output(pluginLayer.get_output(0))
# mark_output函数的作用是标记网络的输出张量,其中的参数是张量。
engineString = builder.build_serialized_network(network, config)
# build_serialized_network函数的作用是构建序列化的网络,其中的两个参数分别是网络和配置。
运行期
  • TensorRT 为 Plugin 提供输入输出张量的地址,workspace 的地址,以及所在的 stream

完整代码

import ctypes
import os

import numpy as np
import tensorrt as trt
from cuda import cudart

soFile = "./AddScalarPlugin.so"
np.set_printoptions(precision=3, linewidth=200, suppress=True)
# np.set_printoptions函数的作用是设置打印时的精度、行宽、是否使用科学计数法等。其中的三个
# 参数含义分别是:precision:设置浮点数的精度,即小数点后的位数;linewidth:设置输出的行宽;suppress:当suppress=True时,表示不输出小数点后面的数字,即将小数部分四舍五入
np.random.seed(31193)
cudart.cudaDeviceSynchronize()

def printArrayInformation(x, info="", n=5):
    if 0 in x.shape:
        print('%s:%s' % (info, str(x.shape)))
        return
    x = x.astype(np.float32)
    print( '%s:%s,SumAbs=%.5e,Var=%.5f,Max=%.5f,Min=%.5f,SAD=%.5f'%( \
        info,str(x.shape),np.sum(abs(x)),np.var(x),np.max(x),np.min(x),np.sum(np.abs(np.diff(x.reshape(-1)))) ))
    print('\t', x.reshape(-1)[:n], x.reshape(-1)[-n:])
    return

def check(a, b, weak=False, checkEpsilon=1e-5, info=""):
    if a.shape != b.shape:
        print("Error shape: A%s : B%s" % (str(a.shape), str(b.shape)))
        return
    if weak:
        a = a.astype(np.float32)
        b = b.astype(np.float32)
        res = np.all(np.abs(a - b) < checkEpsilon)
    else:
        res = np.all(a == b)
    maxAbsDiff = np.max(np.abs(a - b))
    meanAbsDiff = np.mean(np.abs(a - b))
    maxRelDiff = np.max(np.abs(a - b) / (np.abs(b) + checkEpsilon))
    meanRelDiff = np.mean(np.abs(a - b) / (np.abs(b) + checkEpsilon))
    res = "%s:%s,MaxAbsDiff=%.2e,MeanAbsDiff=%.2e,MaxRelDiff=%.2e,MeanRelDiff=%.2e," % (info, res, maxAbsDiff, meanAbsDiff, maxRelDiff, meanRelDiff)
    index = np.argmax(np.abs(a - b))
    valueA, valueB= a.flatten()[index], b.flatten()[index]
    shape = a.shape
    indexD = []
    for i in range(len(shape) - 1, -1, -1):
        x = index % shape[i]
        indexD = [x] + indexD
        index = index // shape[i]
    res += "WorstPair=(%f:%f)at%s" %(valueA, valueB, str(indexD))
    print(res)
    return

def addScalarCPU(inputH, scalar):
    return [inputH[0] + scalar]

def getAddScalarPlugin(scalar):
    for c in trt.get_plugin_registry().plugin_creator_list:
        #print(c.name)
        if c.name == "AddScalar":
            parameterList = []
            parameterList.append(trt.PluginField("scalar", np.float32(scalar), trt.PluginFieldType.FLOAT32))
            # PluginField类的作用是定义插件的属性,其中的三个参数分别是属性的名称、属性的值和属性的数据类型。
            return c.create_plugin(c.name, trt.PluginFieldCollection(parameterList))
            # create_plugin函数的作用是创建一个插件,其中的两个参数分别是插件的名称和插件的属性集合。
    return None

def run(shape, scalar):
    testCase = "<shape=%s,scalar=%f>" % (shape, scalar)
    trtFile = "./model-Dim%s.plan" % str(len(shape))
    print("Test %s" % testCase)
    logger = trt.Logger(trt.Logger.ERROR)
    trt.init_libnvinfer_plugins(logger, '')
    # trt.init_libnvinfer_plugins函数的作用是初始化TensorRT库中的插件,其中的两个参数分别是日志级别和插件库的路径。
    ctypes.cdll.LoadLibrary(soFile)
    # ctypes.cdll.LoadLibrary函数的作用是加载指定的动态链接库,其中的参数是动态链接库的路径。
    if os.path.isfile(trtFile):
        with open(trtFile, "rb") as f:
            engine = trt.Runtime(logger).deserialize_cuda_engine(f.read())
        if engine == None:
            print("Failed loading engine!")
            return
        print("Succeeded loading engine!")
    else:
        builder = trt.Builder(logger)
        network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))
        profile = builder.create_optimization_profile()
        config = builder.create_builder_config()

        inputT0 = network.add_input("inputT0", trt.float32, [-1 for i in shape])
        profile.set_shape(inputT0.name, [1 for i in shape], [8 for i in shape], [32 for i in shape])
        config.add_optimization_profile(profile)

        pluginLayer = network.add_plugin_v2([inputT0], getAddScalarPlugin(scalar))
        # add_plugin_v2函数的作用是向网络中添加一个插件层,其中的两个参数分别是输入张量列表和插件。
        network.mark_output(pluginLayer.get_output(0))
        # mark_output函数的作用是标记网络的输出张量,其中的参数是张量。
        engineString = builder.build_serialized_network(network, config)
        # build_serialized_network函数的作用是构建序列化的网络,其中的两个参数分别是网络和配置。
        if engineString == None:
            print("Failed building engine!")
            return
        print("Succeeded building engine!")
        with open(trtFile, "wb") as f:
            f.write(engineString)
        engine = trt.Runtime(logger).deserialize_cuda_engine(engineString)
        # deserialize_cuda_engine函数的作用是反序列化一个CUDA引擎,其中的参数是序列化的引擎。

    nIO = engine.num_io_tensors
    # num_io_tensors属性的作用是获取引擎的输入输出张量的数量。
    lTensorName = [engine.get_tensor_name(i) for i in range(nIO)]
    # get_tensor_name函数的作用是获取引擎的输入输出张量的名称。
    nInput = [engine.get_tensor_mode(lTensorName[i]) for i in range(nIO)].count(trt.TensorIOMode.INPUT)
    # get_tensor_mode函数的作用是获取引擎的输入输出张量的模式,其中的参数是张量的名称。
    context = engine.create_execution_context()
    context.set_input_shape(lTensorName[0], shape)
    #for i in range(nIO):
    #    print("[%2d]%s->" % (i, "Input " if i < nInput else "Output"), engine.get_tensor_dtype(lTensorName[i]), engine.get_tensor_shape(lTensorName[i]), context.get_tensor_shape(lTensorName[i]), lTensorName[i])

    bufferH = []
    bufferH.append(np.arange(np.prod(shape), dtype=np.float32).reshape(shape))
    # np.arange函数的作用是创建一个等差数组,其中的参数是数组的大小。np.prod函数的作用是计算数组的元素个数。
    for i in range(nInput, nIO):
        bufferH.append(np.empty(context.get_tensor_shape(lTensorName[i]), dtype=trt.nptype(engine.get_tensor_dtype(lTensorName[i]))))
        # 初始化一个空数组,数组的形状是引擎的输入输出张量的形状,数组的数据类型是引擎的输出张量的数据类型。
    bufferD = []
    for i in range(nIO):
        bufferD.append(cudart.cudaMalloc(bufferH[i].nbytes)[1])
        # cudart.cudaMalloc函数的作用是在GPU上分配一块内存,其中的参数是内存的大小。
        # 为推理输入输出张量分配内存。

    for i in range(nInput):
        cudart.cudaMemcpy(bufferD[i], bufferH[i].ctypes.data, bufferH[i].nbytes, cudart.cudaMemcpyKind.cudaMemcpyHostToDevice)
        # cudart.cudaMemcpy函数的作用是在GPU之间复制内存,其中的四个参数分别是目标内存、源内存、内存的大小和复制的方向。
        # 将模型的输入张量从CPU复制到GPU。

    for i in range(nIO):
        context.set_tensor_address(lTensorName[i], int(bufferD[i]))
        # set_tensor_address函数的作用是设置张量的地址,其中的两个参数分别是张量的名称和地址。

    context.execute_async_v3(0)
    # execute_async_v3函数的作用是异步执行推理,其中的参数是批次大小。

    for i in range(nInput, nIO):
        cudart.cudaMemcpy(bufferH[i].ctypes.data, bufferD[i], bufferH[i].nbytes, cudart.cudaMemcpyKind.cudaMemcpyDeviceToHost)
        # 将模型的输出张量从GPU复制到CPU。

    outputCPU = addScalarCPU(bufferH[:nInput], scalar)
    """
    for i in range(nInput):
        printArrayInformation(bufferH[i])
    for i in range(nInput, nIO):
        printArrayInformation(bufferH[i])
    for i in range(nInput, nIO):
        printArrayInformation(outputCPU[i - nInput])
    """
    check(bufferH[nInput:][0], outputCPU[0], True)

    for b in bufferD:
        cudart.cudaFree(b)
        # 释放GPU上的内存。
    print("Test %s finish!\n" % testCase)

if __name__ == "__main__":
    os.system("rm -rf ./*.plan")

    run([32], 1)
    run([32, 32], 1)
    run([16, 16, 16], 1)
    run([8, 8, 8, 8], 1)
    run([32], 1)
    run([32, 32], 1)
    run([16, 16, 16], 1)
    run([8, 8, 8, 8], 1)

    print("Test all finish!")
好的,以下是一个简单的TensorRT C++部署模板,你可以根据自己的需求进行修改: ```c++ #include <iostream> #include <fstream> #include <sstream> #include <vector> #include <ctime> #include "NvInfer.h" #include "NvOnnxParser.h" #include "NvUffParser.h" #include "NvInferPlugin.h" #include "NvUtils.h" using namespace nvinfer1; using namespace nvonnxparser; using namespace nvuffparser; using namespace plugin; // 定义输入和输出张量大小 const int BATCH_SIZE = 1; const int INPUT_CHANNEL = 3; const int INPUT_HEIGHT = 224; const int INPUT_WIDTH = 224; const int OUTPUT_SIZE = 1000; // 定义TensorRT日志回调函数 void logCallback(const char* msg, const char* /*file*/, int /*line*/) { std::cout << "[TensorRT] " << msg << std::endl; } int main(int argc, char** argv) { // 创建Logger对象 Logger logger; logger.log(ILogger::Severity::kINFO, "TensorRT version: " + std::to_string(getInferLibVersion())); // 创建Builder对象 IBuilder* builder = createInferBuilder(logger); if (!builder) { std::cerr << "Failed to create builder." << std::endl; return -1; } // 创建NetworkDefinition对象 INetworkDefinition* network = builder->createNetwork(); if (!network) { std::cerr << "Failed to create network." << std::endl; return -1; } // 创建ONNX解析器对象 IParser* parser = createParser(*network, logger); if (!parser) { std::cerr << "Failed to create parser." << std::endl; return -1; } // 从ONNX模型文件中解析网络结构 std::string onnxModelFile = "model.onnx"; parser->parseFromFile(onnxModelFile.c_str(), static_cast<int>(ILogger::Severity::kWARNING)); if (parser->getNbErrors() > 0) { std::cerr << "Failed to parse ONNX model." << std::endl; return -1; } // 构建推理引擎 builder->setMaxBatchSize(BATCH_SIZE); builder->setMaxWorkspaceSize(1 << 30); builder->setFp16Mode(true); builder->setInt8Mode(false); ICudaEngine* engine = builder->buildCudaEngine(*network); if (!engine) { std::cerr << "Failed to build engine." << std::endl; return -1; } // 创建ExecutionContex对象 IExecutionContext* context = engine->createExecutionContext(); if (!context) { std::cerr << "Failed to create execution context." << std::endl; return -1; } // 分配输入和输出内存 std::vector<float> inputData(BATCH_SIZE * INPUT_CHANNEL * INPUT_HEIGHT * INPUT_WIDTH); std::vector<float> outputData(BATCH_SIZE * OUTPUT_SIZE); // 生成随机数据作为输入 srand(time(nullptr)); for (size_t i = 0; i < inputData.size(); i++) { inputData[i] = static_cast<float>(rand()) / RAND_MAX; } // 将输入数据传输到显存中 void* inputDevice = nullptr; cudaMalloc(&inputDevice, inputData.size() * sizeof(float)); cudaMemcpy(inputDevice, inputData.data(), inputData.size() * sizeof(float), cudaMemcpyHostToDevice); // 分配GPU内存作为输出 void* outputDevice = nullptr; cudaMalloc(&outputDevice, outputData.size() * sizeof(float)); // 创建CUDA流 cudaStream_t stream = nullptr; cudaStreamCreate(&stream); // 执行推理 std::vector<void*> bindings(2); bindings[0] = inputDevice; bindings[1] = outputDevice; context->enqueueV2(bindings.data(), stream, nullptr); cudaStreamSynchronize(stream); // 将输出数据从显存中传输回CPU内存 cudaMemcpy(outputData.data(), outputDevice, outputData.size() * sizeof(float), cudaMemcpyDeviceToHost); // 打印输出结果 for (size_t i = 0; i < outputData.size(); i++) { std::cout << outputData[i] << " "; } std::cout << std::endl; // 释放资源 cudaStreamDestroy(stream); cudaFree(inputDevice); cudaFree(outputDevice); context->destroy(); engine->destroy(); network->destroy(); parser->destroy(); builder->destroy(); return 0; } ``` 在这个模板中,我们首先创建了一个Logger对象用于记录TensorRT的日志信息,然后创建了一个Builder对象用于构建推理引擎,接着创建了一个NetworkDefinition对象用于描述网络结构,然后创建了一个ONNX解析器对象用于从ONNX模型文件中解析网络结构,接着使用Builder对象构建推理引擎,创建ExecutionContex对象用于推理,分配输入和输出内存并生成随机数据作为输入,将输入数据传输到显存中,然后执行推理,将输出数据从显存中传输回CPU内存,并打印输出结果。最后释放资源。 需要注意的是,在实际应用中,你需要根据自己的模型及数据进行修改。同时,这里使用了FP16精度模式和CUDA流进行加速,如果你的GPU不支持FP16精度模式,可以将其设置为false。如果你不需要使用CUDA流,可以直接传入nullptr。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值