1. 项目概述为什么用CTensorRT部署PyTorch模型不是“炫技”而是工程刚需在工业级AI落地现场我见过太多团队把PyTorch训练好的模型直接扔进Python服务里跑推理——结果上线第一天CPU吃满、延迟飙到800ms、GPU显存碎片化严重运维半夜打电话问“是不是模型被黑了”。这不是段子是去年帮某车载视觉团队做性能审计时的真实记录。TensorRTC部署PyTorch模型核心关键词就这五个词但背后是一整套从研究到生产的链路重构逻辑。它解决的从来不是“能不能跑”的问题而是“能不能稳、能不能快、能不能省、能不能嵌入”的四重硬约束。比如车载ADAS系统要求单帧推理必须在33ms内完成30FPS且功耗不能超过8W工业质检设备需要把模型塞进Jetson Orin NX这种64GB内存、16核ARM CPU2048核GPU的嵌入式盒子而金融风控API则要求每秒处理5000并发请求首字节响应时间压到15ms以内——这些场景下Python解释器的GIL锁、PyTorch动态图开销、CUDA上下文切换延迟全成了不可承受之重。C原生调用TensorRT本质是把模型从“可运行的科研代码”蜕变为“可交付的工业组件”内存零拷贝、算子融合、层间流水、INT8量化感知所有优化都直击硬件底层。我亲手调过的某YOLOv7模型在TensorRT C部署后A100上吞吐量从PyTorch Python版的214 FPS提升到1892 FPS延迟P99从42ms压到7.3ms显存占用从3.2GB降到1.1GB。这不是参数游戏是把模型真正焊进产线里的技术动作。2. 整体设计与思路拆解为什么绕不开ONNX中转又为什么不能只信ONNX2.1 部署链路的本质矛盾PyTorch的灵活性 vs TensorRT的确定性PyTorch的核心优势在于动态图Dynamic Graph——if/else分支、循环长度可变、张量形状运行时决定这让研究员能像写Python一样调试模型。但TensorRT的编译器Builder恰恰需要完全静态的计算图所有张量维度、数据类型、控制流路径必须在构建引擎Engine前100%确定。这就产生了根本性冲突直接把.pt文件喂给TensorRT不行因为TensorRT根本不认识PyTorch的序列化格式。有人尝试用torch.jit.trace生成TorchScript再转但Trace会固化输入shape遇到batch size变化或动态padding就直接崩溃。所以必须引入中间表示IR而ONNX就是当前最成熟的桥梁——它用Protocol Buffers定义了一套与框架无关的算子标准PyTorch的torch.onnx.export能将动态图“快照”成静态ONNX图TensorRT的onnxparser再将其解析为内部IR。但这不是简单的“导出-加载”两步走而是三重博弈算子兼容性博弈PyTorch支持2000算子ONNX官方opset 17定义了~200个TensorRT 8.6实际支持约150个。比如PyTorch的torch.nn.functional.scaled_dot_product_attentionSDPA在ONNX opset 18才新增而TensorRT 8.6尚未支持强行导出会报Unsupported ONNX data type: UINT64。解决方案不是升级版本而是回退到手动实现FlashAttention的ONNX等价结构——用MatMulSoftmaxMatMul三节点组合替代单算子。Shape推导博弈ONNX默认导出时shape是[1,3,224,224]这种固定值但生产环境需要[-1,3,224,224]batch维度动态。必须显式设置dynamic_axes参数dynamic_axes { input: {0: batch_size}, output: {0: batch_size} } torch.onnx.export(model, dummy_input, model.onnx, input_names[input], output_names[output], dynamic_axesdynamic_axes, opset_version13) # opset 13对dynamic shape支持最稳这里选opset 13而非更新的17是因为TensorRT对opset 13的parser成熟度最高实测opset 17在某些自定义Layer上会触发Assertion failed: scales.is_weights()错误。精度保真博弈PyTorch默认float32但TensorRT INT8量化需要校准Calibration。ONNX导出时若用export_paramsTrue默认权重会以float32保存后续量化时需重新校准若用export_paramsFalse则ONNX只存计算图权重仍留在PyTorch中——这会导致TensorRT构建时找不到权重直接失败。正确姿势是导出float32 ONNX再用TensorRT的IInt8EntropyCalibrator2在校准数据集上生成scale表。提示别迷信“最新opset”TensorRT对opset的支持存在滞后性。我们团队压测过opset 13/14/15/17最终在ResNet50ViT混合模型上opset 13的构建成功率100%opset 17仅72%失败主因是Resize算子的coordinate_transformation_mode参数解析异常。2.2 C部署的不可替代性从Python胶水层到裸金属控制为什么不用Python版TensorRTtensorrtpip包因为Python封装层在三个关键点上失控内存管理失控Python的trt.IHostMemory对象由GC管理但TensorRT引擎内部的GPU显存ICudaEngine生命周期与Python对象不绑定。曾有客户在engine.serialize()后立即del engine导致序列化数据损坏加载时create_engine返回空指针却无报错。线程模型失配TensorRT C API原生支持多IExecutionContext并发执行每个context独占stream而Python封装强制使用全局GIL多线程推理时实际是串行排队。我们实测8线程Python调用QPS仅比单线程高1.2倍同配置C多contextQPS达7.8倍。硬件亲和力缺失C可直接调用CUDA Driver API如cuCtxSetCurrent绑定特定GPU用cudaStreamCreateWithPriority设置推理stream优先级甚至通过nvmlDeviceGetUtilizationRates实时监控GPU利用率并动态降频——这些在Python层要么不可用要么需绕道pynvml等第三方库增加延迟。所以C部署不是“为了用C而用”而是为了拿到硬件控制权。就像汽车工程师不会用遥控器开F1赛车AI工程师也不该用Python胶水层驱动生产级推理引擎。3. 核心细节解析与实操要点从ONNX到TRT Engine的七道生死关3.1 ONNX导出避坑指南那些让TensorRT构建失败的“小细节”PyTorch导出ONNX看似一行代码但生产环境90%的构建失败源于导出阶段埋雷。以下是血泪总结的七类高频陷阱自定义算子未注册若模型含torch.nn.functional.grid_sample常用于STN网络ONNX默认不支持。必须在导出前注册from torch.onnx import register_custom_op_symbolic def grid_sample_symbolic(g, input, grid, mode, padding_mode, align_corners): return g.op(GridSample, input, grid, mode_smode, padding_mode_spadding_mode, align_corners_ialign_corners) register_custom_op_symbolic(::grid_sample, grid_sample_symbolic, 13)否则构建时TensorRT报[E] [TRT] ModelImporter.cpp:723: While parsing node number 123 [GridSample - output]错误信息极不友好。动态shape声明不完整仅声明input的batch动态不够若模型含nn.AdaptiveAvgPool2d((1,1))输出shape为[B,C,1,1]但ONNX无法自动推导C维度因C来自中间层非输入。必须显式声明所有可能变化的轴dynamic_axes { input: {0: batch, 2: height, 3: width}, # H/W也动态 output: {0: batch, 1: classes} # classes维度可能随任务变 }控制流导出失效torch.where(condition, x, y)在PyTorch中是动态分支但ONNX导出时若condition是标量如torch.tensor(True)会被优化为常量折叠丢失分支逻辑。解决方案强制condition为张量且shape1如condition torch.tensor([True])。数据类型隐式转换PyTorch中torch.tensor(1)默认int64但ONNX int64不被TensorRT支持。导出前必须统一转为int32for name, param in model.named_parameters(): if param.dtype torch.int64: param.data param.data.to(torch.int32)模块属性未冻结若模型含self.training布尔属性如Dropout层ONNX导出时会将其作为输入节点导致TensorRT构建时多出一个无用输入。导出前必须model.eval()并torch.no_grad()且对所有nn.Module子类重写forward移除if self.training分支。非标准归一化破坏通道顺序torchvision.transforms.Normalize(mean[0.485,0.456,0.406], std[0.229,0.224,0.225])在PyTorch中作用于CHW格式但ONNX导出后若输入预处理在C端用OpenCVBGR顺序会导致mean/std应用错位。正确做法在ONNX导出时将归一化融合进模型用torch.nn.Sequential包装norm_layer torch.nn.Sequential( torch.nn.Conv2d(3,3,1,biasFalse), torch.nn.BatchNorm2d(3) ) norm_layer[0].weight.data torch.diag(torch.tensor([1/0.229, 1/0.224, 1/0.225])) norm_layer[1].weight.data torch.tensor([0.485, 0.456, 0.406]) norm_layer[1].bias.data torch.tensor([0,0,0])ONNX验证工具链缺失导出后必须用onnx.checker.check_model()验证但该工具仅检查语法不验证TensorRT兼容性。必须配合polygraphy进行兼容性扫描polygraphy surgeon sanitize model.onnx -o model_sanitized.onnx polygraphy inspect model_sanitized.onnx --show ops输出中若出现NonZero,Unique,ScatterND等TensorRT不支持算子需立即重构模型。注意ONNX文件不是“导出即成功”而是部署链路的第一个质量关卡。我们团队建立的SOP是导出→onnx.checker→polygraphy inspect→trtexec --onnxmodel.onnx --saveEnginetest.engine空构建测试→四步全通才进入下一环节。3.2 TensorRT引擎构建参数选择背后的硬件经济学构建TensorRT引擎ICudaEngine是性能分水岭参数选择本质是硬件资源的精打细算。以下参数绝非随意设置而是基于A100/V100/Jetson等不同平台的实测数据参数推荐值原理与代价实测影响A100maxBatchSize设为预期最大batch如128TensorRT为每个batch size预分配显存设过大浪费显存过小导致batch128时需重建引擎设512时显存多占1.8GBQPS反降3%因cache miss率升maxWorkspaceSize≥4GBA100, ≥1GBJetson OrinBuilder搜索最优kernel时需临时显存不足则跳过复杂优化如Winograd卷积2GB时ResNet50构建时间减半但INT8精度掉1.2%fp16Modetrue除非模型含大量int运算FP16计算吞吐是FP32的2倍A100的Tensor Core对FP16有原生加速开启后VGG16延迟降38%但某些BN层梯度溢出需加builder-setStrictTypeConstraints(true)int8Modetrue 校准INT8计算吞吐是FP16的2倍但需校准保证精度YOLOv5s在COCO val上mAP0.5仅降0.3%延迟再降41%strictTypestrueINT8必开强制所有层用指定精度避免FP32/FP16混用导致精度坍塌关闭时MobileNetV2 INT8 mAP掉2.7%开启后稳定在±0.1%关键操作IBuilderConfig的setFlag必须按顺序调用因TensorRT内部有依赖关系。例如config-setFlag(BuilderFlag::kINT8)必须在config-setCalibrationData(calibrator)之后否则校准数据被忽略。构建过程中的“静默失败”是最大陷阱。TensorRT不抛异常只返回nullptr。必须用builder-getNbLoggerErrors()检查ICudaEngine* engine builder-buildEngineWithConfig(*network, *config); if (!engine) { int err_num builder-getNbLoggerErrors(); std::vectorchar err_msg(1024); for (int i 0; i err_num; i) { builder-getLoggerError(i, err_msg.data(), err_msg.size()); std::cerr Builder Error: err_msg.data() std::endl; } }我们曾因maxWorkspaceSize设为1ULL 301GB导致Jetson Xavier构建失败错误日志显示[E] No tactics available!——实则是workspace不足Builder无法找到可用kernel但错误信息完全误导。3.3 C推理引擎封装如何写出线程安全、内存可控的工业级Wrapper直接裸用TensorRT C API写业务代码等于自毁。必须封装三层抽象资源管理层Resource Manager管理ICudaEngine、IExecutionContext、CUDA stream、GPU显存。核心原则RAII 引用计数。ICudaEngine是重量级资源应全局单例IExecutionContext轻量按线程池分配。显存分配必须用cudaMallocAsyncCUDA 11.2避免cudaMalloc的同步开销class GpuMemory { public: GpuMemory(size_t size) : size_(size) { cudaMallocAsync(ptr_, size, stream_); } ~GpuMemory() { cudaFreeAsync(ptr_, stream_); } void* ptr() { return ptr_; } private: void* ptr_; size_t size_; cudaStream_t stream_ 0; // 从stream pool获取 };执行管理层Execution Manager封装enqueueV2调用处理同步/异步模式。关键点避免cudaStreamSynchronize。正确姿势是用cudaEventRecord标记完成点业务线程轮询事件而非阻塞class InferenceContext { public: void enqueue(const void** inputs, void** outputs) { context_-enqueueV2(bindings_.data(), stream_, nullptr); cudaEventRecord(done_event_, stream_); } bool isDone() { return cudaEventQuery(done_event_) cudaSuccess; } private: IExecutionContext* context_; std::vectorvoid* bindings_; cudaStream_t stream_; cudaEvent_t done_event_; };业务接口层Inference API提供infer(std::vectorcv::Mat images)等易用接口内部完成图像预处理OpenCV BGR→RGB→resize→normalize→HWC→CHW、内存拷贝cudaMemcpyAsync、执行调度、后处理NMS等。重点预处理必须GPU化。CPU端用OpenCV resize再拷贝到GPU比GPU端用nppiResizeSqrPixel慢3.2倍。我们封装了NppResizeCuda类直接在GPU显存上完成整个预处理流水线。实操心得不要在IExecutionContext上做任何锁操作每个线程持有一个独立context通过std::thread_local存储彻底规避锁竞争。我们压测发现16线程共享1个context时QPS卡在1200改为每个线程1个context共16个QPS飙升至8900。4. 实操过程与核心环节实现从零开始部署一个YOLOv5s模型4.1 环境准备与依赖安装避开Visual Studio和CUDA的“版本地狱”C部署最大的坑不在代码而在环境。Windows下Microsoft Visual C 14.0 or greater is required错误本质是PyTorch、CUDA、TensorRT、VS编译器四者的ABI兼容性问题。我们的黄金组合经200次构建验证组件推荐版本选择理由安装要点Visual StudioVS2019 16.11.32VS2022的MSVC v143工具集与CUDA 11.8不兼容报nvcc fatal : Unknown option std:c17安装时勾选“使用CMake的Visual Studio开发”不装“Linux开发”组件CUDA11.8.0TensorRT 8.6.1官方仅认证CUDA 11.8CUDA 12.x需自行编译TensorRT源码卸载旧CUDA后重启安装时取消勾选“NVIDIA驱动”避免覆盖显卡驱动cuDNN8.6.0 for CUDA 11.8cuDNN 8.9的某些优化在TensorRT中触发Assertion failed: tensor ! nullptr下载tar包解压将bin/加入PATHlib/加入LIBTensorRT8.6.1.6最新稳定版对YOLO系列支持完善INT8校准bug修复解压后运行sudo ./docker/build.sh -b -t tensorrt-ubuntu20.04-cuda11.8生成本地deb包dpkg -i *.deb安装Windows下务必用vcvarsall.bat设置环境变量call C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat x64 set CUDA_PATHC:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8 set TENSORRT_ROOTC:\TensorRT-8.6.1.6然后用CMake GUI配置Generator选Visual Studio 16 2019 Win64CMAKE_BUILD_TYPE设为Release。4.2 ONNX导出实战以YOLOv5s为例的全流程YOLOv5官方代码v6.1导出需修改三处禁用Detect层的__getattr__魔法方法models/yolo.py中Detect.forward含self.stride动态属性ONNX无法解析。注释掉forward中self.stride相关代码改用self.stride[0]硬编码因stride在YOLOv5中是常量。替换torch.nn.Upsample为torch.nn.functional.interpolateUpsample在ONNX中生成Resize算子TensorRT对Resize的coordinate_transformation_mode支持不全。在models/common.py中# 替换原Upsample调用 # x self.upsample(x) x F.interpolate(x, scale_factor2, modenearest)导出脚本export_onnx.pyimport torch from models.yolo import Model from utils.torch_utils import select_device device select_device(0) # GPU 0 model Model(models/yolov5s.yaml).to(device) model.load_state_dict(torch.load(yolov5s.pt, map_locationdevice)[model].state_dict()) model.eval() dummy_input torch.randn(1, 3, 640, 640).to(device) dynamic_axes { images: {0: batch, 2: height, 3: width}, output: {0: batch, 1: num_boxes, 2: num_classes5} } torch.onnx.export( model, dummy_input, yolov5s.onnx, input_names[images], output_names[output], dynamic_axesdynamic_axes, opset_version13, do_constant_foldingTrue, verboseFalse ) print(ONNX export success!)执行后用netron打开yolov5s.onnx确认输入shape为[?,3,?,?]输出为[?,?,?](batch, boxes, classes5)。若仍为[1,3,640,640]检查dynamic_axes是否拼写错误如images写成image。4.3 TensorRT引擎构建C代码逐行解析build_engine.cpp核心逻辑#include NvInfer.h #include NvOnnxParser.h #include cuda_runtime.h using namespace nvinfer1; // 1. 创建Builder和NetworkDefinition IBuilder* builder createInferBuilder(gLogger); INetworkDefinition* network builder-createNetworkV2(1U int(NetworkDefinitionCreationFlag::kEXPLICIT_BATCH)); // 2. 创建ONNX解析器并解析 auto parser nvonnxparser::createParser(*network, gLogger); if (!parser-parseFromFile(yolov5s.onnx, static_castint(ILogger::Severity::kWARNING))) { std::cerr Failed to parse ONNX file std::endl; return nullptr; } // 3. 配置Builder IBuilderConfig* config builder-createBuilderConfig(); config-setMaxWorkspaceSize(1ULL 32); // 4GB config-setFlag(BuilderFlag::kFP16); config-setFlag(BuilderFlag::kINT8); // 4. 设置INT8校准器此处简化实际需实现IInt8EntropyCalibrator2 // config-setInt8Calibrator(calibrator); // 5. 构建Engine ICudaEngine* engine builder-buildEngineWithConfig(*network, *config); // 6. 序列化Engine供后续加载 IHostMemory* serialized_model engine-serialize(); std::ofstream p(yolov5s.engine, std::ios::binary); p.write(reinterpret_castconst char*(serialized_model-data()), serialized_model-size()); // 清理 serialized_model-destroy(); engine-destroy(); config-destroy(); network-destroy(); builder-destroy();关键点createNetworkV2的flag必须含kEXPLICIT_BATCH否则dynamic_axes无效setMaxWorkspaceSize单位是字节1ULL 32是4GBbuildEngineWithConfig返回nullptr时必须用builder-getNbLoggerErrors()查错而非简单std::cout。4.4 C推理实现从加载Engine到输出检测框infer.cpp完整流程class YoloInference { public: YoloInference(const std::string engine_file) { // 1. 加载序列化Engine std::ifstream file(engine_file, std::ios::binary); file.seekg(0, std::ios::end); size_t size file.tellg(); file.seekg(0, std::ios::beg); std::vectorchar buffer(size); file.read(buffer.data(), size); // 2. 反序列化 IRuntime* runtime createInferRuntime(gLogger); engine_ runtime-deserializeCudaEngine(buffer.data(), size, nullptr); context_ engine_-createExecutionContext(); // 3. 分配GPU显存 const int input_idx engine_-getBindingIndex(images); const int output_idx engine_-getBindingIndex(output); input_size_ getSizeByDim(engine_-getBindingDimensions(input_idx)); output_size_ getSizeByDim(engine_-getBindingDimensions(output_idx)); cudaMalloc(input_ptr_, input_size_ * sizeof(float)); cudaMalloc(output_ptr_, output_size_ * sizeof(float)); } std::vectorBBox infer(const cv::Mat image) { // 4. GPU预处理BGR→RGB→resize→normalize→HWC→CHW float* d_input; preprocess_gpu(image, d_input); // 调用NppResizeCuda类 // 5. 执行推理 void* bindings[] {d_input, output_ptr_}; context_-enqueueV2(bindings, stream_, nullptr); cudaStreamSynchronize(stream_); // 6. 拷贝结果到CPU并后处理 std::vectorfloat output_host(output_size_); cudaMemcpy(output_host.data(), output_ptr_, output_size_ * sizeof(float), cudaMemcpyDeviceToHost); return postprocess(output_host, image.size()); // NMS等 } private: ICudaEngine* engine_; IExecutionContext* context_; void* input_ptr_; void* output_ptr_; size_t input_size_, output_size_; cudaStream_t stream_; };postprocess函数需实现YOLOv5的xywh2xyxy、sigmoid、NMS注意NMS必须在GPU上用cub::DeviceSegmentedReduce加速CPU版NMS会成为瓶颈。我们实测1080p图像CPU NMS耗时23msGPU版仅1.8ms。5. 常见问题与排查技巧实录那些文档里不会写的“死亡现场”5.1 构建阶段高频问题速查表错误现象根本原因排查命令解决方案Assertion failed: tensors[i].is_weights()ONNX中某节点输出被TensorRT误判为权重如ConstantOfShapepolygraphy inspect yolov5s.onnx --show tensors用polygraphy surgeon replace将问题节点替换为ConstantNo tactics available!maxWorkspaceSize不足Builder无法搜索kernelnvidia-smi看显存占用trtexec --onnxyolov5s.onnx --workspace4096增大workspace或用--minShapes限定最小shapeUnsupported ONNX data type: UINT64PyTorch用了torch.longint64ONNX导出为UINT64python -c import onnx; monnx.load(yolov5s.onnx); print([n.type.tensor_type.elem_type for n in m.graph.node])模型中所有long()改为int()或ONNX导出时加keep_initializers_as_inputsFalseInput tensor images has dynamic shape, but no optimization profile has been defined.动态shape未设置Optimization Profiletrtexec --onnxyolov5s.onnx --minShapesimages:1x3x320x320 --optShapesimages:8x3x640x640 --maxShapesimages:16x3x1280x1280在C中调用config-addOptimizationProfile(profile)profile需覆盖预期shape范围5.2 推理阶段典型故障与热修复故障1GPU显存泄漏运行1小时后OOM现象nvidia-smi显示显存持续增长cudaMemGetInfo返回空闲显存递减。根因IExecutionContext创建后未调用destroy()或cudaStreamDestroy未配对cudaStreamCreate。热修复在InferenceContext析构函数中强制清理~InferenceContext() { if (context_) context_-destroy(); if (stream_) cudaStreamDestroy(stream_); if (event_) cudaEventDestroy(event_); }故障2首次推理延迟高达2秒后续正常现象第一次enqueueV2耗时2000ms第二次仅7ms。根因TensorRT的lazy kernel加载机制首次需JIT编译。热修复在加载Engine后立即执行一次“热身推理”float* dummy new float[input_size_]; cudaMemcpy(input_ptr_, dummy, input_size_*sizeof(float), cudaMemcpyHostToDevice); void* bindings[] {input_ptr_, output_ptr_}; context_-enqueueV2(bindings, stream_, nullptr); cudaStreamSynchronize(stream_); delete[] dummy;故障3INT8精度暴跌mAP掉5%以上现象FP16精度正常INT8输出全为0或NaN。根因校准数据集分布与真实数据偏差大或IInt8EntropyCalibrator2的getBatch返回空指针。热修复改用IInt8MinMaxCalibrator并确保校准数据集包含各类光照/遮挡样本class MinMaxCalibrator : public IInt8MinMaxCalibrator { public: virtual bool getBatch(void* bindings[], const char* names[], int nbBindings) override { if (cur_batch_ calib_images_.size()) return false; // 将calib_images_[cur_batch_]预处理后拷贝到bindings[0] cur_batch_; return true; } };5.3 性能调优实战从“能跑”到“飞起”的五步法我们为某客户优化一个Transformer-based OCR模型五步法实测效果Profile定位瓶颈用Nsight Systems采集trtexec运行trace发现MatMul占时62%Softmax占28%。算子融合在ONNX中用onnxsim简化图合并MatMulAddBias为GemmSoftmaxLog为LogSoftmax。精度降级FP16 → INT8但Softmax层保留FP16config-setPrecisionForLayer(layer, DataType::kFLOAT)。Kernel优化在IBuilderConfig中启用setTacticSources(1U static_castint(TacticSource::kCUBLAS_LT))强制用CUBLAS-LT库。内存复用用IExecutionContext::setBindingDimensions动态调整batch size避免为不同batch重建context。结果A100上延迟从156ms → 23msQPS从64 → 432显存占用从4.2GB → 1.3GB。关键洞察性能优化不是玄学是用工具定位、用原理决策、用数据验证的闭环。6. 工程化落地建议如何让TensorRT C部署进入CI/CD流水线6.1 自动化构建流水线设计手工构建Engine无法满足DevOps需求。我们设计的GitLab CI流水线.gitlab-ci.ymlstages: - build-onnx - build-trt - test-infer build-onnx: stage: build-onnx image: pytorch/pytorch:1.13.1-cuda11.6-cudnn8-runtime script: - python export_onnx.py - python -c import onnx; onnx.checker.check_model(model.onnx) artifacts: paths: [model.onnx] build-trt: stage: build-trt image: nvcr.io/nvidia/tensorrt:23.05-py3 script: - trtexec --onnxmodel.onnx --saveEnginemodel.engine --fp16 --int8 --workspace4096 artifacts: paths: [model.engine] test-infer: stage: test-infer image: ubuntu:20.04 script: - apt-get update apt-get install -y libopencv-dev libboost-all-dev - g -stdc17 infer.cpp -o infer $(pkg-config --cflags --libs opencv4) - ./