
简介本资源是一套面向AI算法工程师与C高性能部署开发者的TensorRT加速实践方案聚焦于将Meta开源的SAMSegment Anything Model大模型落地为低延迟、高吞吐的C推理服务。资源提供完整端到端实现涵盖模型导出、TensorRT引擎构建、内存缓冲管理、多线程推理封装及跨平台部署说明特别适配Windows/Linux环境下的工业级图像分割应用开发。压缩包共22个文件含核心C源码.cpp/.h、模型导出与调用脚本.ipynb、中文部署指南.md、示例图像与动图.jpg/.gif、开发配置.json/.dev及Docker构建支持Dockerfile.dev结构清晰、模块解耦便于二次开发与集成。资源大小仅1.74MB轻量但完整已获814人学习下载读者可直接复用代码结构、参考模型量化与推理优化关键步骤并基于提供的truck等实测案例快速验证分割效果与性能表现。1. 把 SAM 模型塞进 TensorRT 加速推理不是调个 API 就完事而是从 .pt 到 .engine 的全链路 C 工程落地你手上有 PyTorch 训练好的 SAMSegment Anything Model权重想在嵌入式设备或边缘服务器上跑出 30 FPS 的实时分割效果——但直接用torch.jit.trace导出再libtorch加载别试了CPU 推理 2 秒一帧GPU 上也卡在显存拷贝和 kernel 启动开销里。这份资源不是“SAM TensorRT”的概念演示而是一套可编译、可调试、可复现的完整 C 工程包它把 SAM 的图像编码器ViT-H、提示编码器point/box/mask、轻量解码器三部分全部拆解为 TensorRT 可识别的 ONNX 子图用trtexec 自定义插件完成算子融合最终在main.cpp里用纯 C 实现输入预处理 → 异步推理 → 后处理掩码生成 → OpenCV 可视化闭环。它不依赖 Python 环境不打包任何.so动态库所有依赖CUDA 11.8、TensorRT 8.6.1、OpenCV 4.8.0都通过 CMakeLists.txt 显式声明版本约束。适合正在做工业质检、医疗影像边缘部署、或 RK3588 / Jetson Orin 上跑视觉算法的 C 工程师——如果你的团队还在用cv2.dnn.readNetFromONNX()做原型验证这份源码就是你跳过 Python 胶水层、直连硬件加速器的「第一块跳板」。2. 为什么必须重写 SAM 的 ONNX 导出逻辑ViT-H 的 patch embedding 和 window attention 无法被默认导出器兼容SAM 的核心瓶颈不在解码器而在 ViT-H 图像编码器。PyTorch 默认的torch.onnx.export对nn.Conv2dnn.Linear混合结构支持尚可但对 ViT 中大量使用的torch.nn.functional.unfoldtorch.einsum组合实现的 window attention会直接报错或生成非法 ONNX opset。更致命的是原始 SAM 的forward函数接受torch.Tensor和Dict混合输入如input_points,input_labelsONNX 不支持动态字典结构。这份资源的how_to_export_vim_h_model.ipynb并非简单调用export.py而是做了三处关键改造2.1 替换unfold为等效Conv2dview链式操作原始 ViT 的 patch embedding 使用F.unfold(x, kernel_size16, stride16)提取 16×16 patch但 ONNX 对unfold的 shape 推导极不稳定。源码中export.h定义了PatchEmbedder类用nn.Conv2d(in_channels3, out_channels1280, kernel_size16, stride16, biasFalse)替代并手动view(-1, 1280, 64*64)拉平——这一步让 ONNX shape 推导从「黑匣子」变成「可静态计算」。// export.h 关键片段 class PatchEmbedder : public torch::nn::Module { public: PatchEmbedder(int64_t embed_dim) : conv(torch::nn::Conv2dOptions(3, embed_dim, 16).stride(16).bias(false)) { register_module(conv, conv); } torch::Tensor forward(torch::Tensor x) { auto patches conv-forward(x); // [B, C, H, W] - [B, C, 64, 64] return patches.view({patches.size(0), patches.size(1), -1}); // [B, C, 4096] } private: torch::nn::Conv2d conv; };提示这段代码必须放在torch.compile之前执行否则torch.compile(..., backendinductor)会尝试优化掉view操作导致 ONNX shape 错误。2.2 将Dict输入重构为固定 shape 的torch.TensorSAM 的提示输入points/labels/boxes是变长 listONNX 要求所有输入 tensor 具有确定 shape。源码中sam_utils.h定义了PromptEncoderInput结构体强制将最多 10 个点坐标 pad 到[1, 10, 2]标签 pad 到[1, 10]boxes pad 到[1, 1, 4]并用torch::zeros({1, 10, 2}, dtypetorch::kFloat32)初始化——所有 padding 值设为-1.0f在后处理时过滤掉。2.3 手动注入MultiHeadAttention的 ONNX 兼容实现原始torch.nn.MultiheadAttention在 ONNX 中会生成Attentionop但 TensorRT 8.6.1 不支持该 op。sam.h中重写了WindowAttention类用torch::matmul(Q, K.transpose(-2,-1))softmaxmatmul三段式实现并禁用torch.nn.functional.scaled_dot_product_attention该函数在 ONNX 中触发SDPAopTRT 不认。导出时显式指定opset_version17避免使用GatherND等 TRT 8.6 不支持的 op。3. CMakeLists.txt 的硬核配置如何让 TensorRT 8.6.1 在 Ubuntu 22.04 CUDA 11.8 下稳定链接这份 C 工程的CMakeLists.txt不是模板生成物而是针对 TensorRT 8.6.1 的 ABI 兼容性反复打磨的结果。很多工程师卡在undefined reference to nvinfer1::ICudaEngine::serialize()这类链接错误根源在于 CUDA 版本、TensorRT 构建方式、以及libnvcaffeparser.so等插件库的加载顺序。以下是必须严格遵循的四步配置3.1 显式指定 CUDA_ARCHITECTURES 并禁用 host compiler fallbackTensorRT 8.6.1 的libnvinfer.so是用 CUDA 11.8 编译的若 CMake 自动探测到系统存在 CUDA 12.x会尝试用nvcc-12.2编译你的main.cpp导致 ABI 不匹配。CMakeLists.txt第 12 行强制锁定set(CMAKE_CUDA_ARCHITECTURES 80) # A100 / RTX 3090 / 4090 set(CMAKE_CUDA_COMPILER /usr/local/cuda-11.8/bin/nvcc) find_package(CUDA REQUIRED)注意CMAKE_CUDA_ARCHITECTURES必须设为单值80不能写75;80—— 多架构会导致nvcc生成 fatbin而 TensorRT 的 plugin loader 只认单一 compute capability。3.2 TensorRT 库路径必须用find_library而非target_link_libraries直接写路径很多教程教人写target_link_libraries(sam_trt PRIVATE /opt/tensorrt/lib/libnvinfer.so)这是灾难性写法。libnvinfer.so依赖libnvonnxparser.so→libnvparsers.so→libcudnn.so.8硬路径链接会跳过RPATH机制运行时报libnvonnxparser.so: cannot open shared object file。正确做法是find_package(TensorRT REQUIRED PATHS /opt/tensorrt) target_link_libraries(sam_trt PRIVATE ${TensorRT_LIBRARIES} ${CUDA_LIBRARIES} ${OpenCV_LIBS} )其中TensorRT_LIBRARIES由FindTensorRT.cmake自动解析所有依赖链确保ldd ./sam_trt输出中libnvonnxparser.so的路径指向/opt/tensorrt/lib/。3.3 OpenCV 必须用-DWITH_CUDAON -DBUILD_opencv_cudacodecON编译SAM 的后处理需要cv::cuda::resize和cv::cuda::threshold加速掩码二值化。若用 apt 安装的libopencv-dev无 CUDA 支持cv::cuda::Stream::Null()会返回空指针导致cudaMalloc失败。Dockerfile.dev中明确构建命令RUN cd /tmp \ wget https://github.com/opencv/opencv/archive/refs/tags/4.8.0.tar.gz \ tar -xzf 4.8.0.tar.gz \ mkdir build cd build \ cmake -D CMAKE_BUILD_TYPERELEASE \ -D CMAKE_INSTALL_PREFIX/usr/local \ -D WITH_CUDAON \ -D OPENCV_DNN_CUDAON \ -D BUILD_opencv_cudacodecON \ -D CUDA_ARCH_BIN8.0 \ -D CUDA_ARCH_PTX \ .. \ make -j$(nproc) make install3.4ThreadPool.h必须用std::thread而非boost::thread源码中ThreadPool.h实现了推理 pipeline 的异步队列但boost::thread在 CUDA 上下文切换时会引发cudaErrorContextIsDestroyed。commons/ThreadPool.h第 45 行明确使用std::threadstd::mutex并在线程启动前调用cudaSetDevice(0)绑定 GPU 上下文void ThreadPool::start() { for (int i 0; i num_threads_; i) { threads_.emplace_back([this, i]() { cudaSetDevice(0); // 关键每个线程必须显式绑定 device while (true) { // ... task loop } }); } }4. 避坑编译成功 ≠ 能跑通这五个现象背后全是 TensorRT 的隐式约束即使 CMake 编译通过、./sam_trt可执行90% 的失败发生在 runtime。以下是我用这套源码在 Jetson AGX OrinCUDA 11.4 / TRT 8.5.2和 RTX 4090CUDA 11.8 / TRT 8.6.1上踩过的血泪坑按现象→原因→解决三段式整理4.1 现象[E] [TRT] INVALID_ARGUMENT: Cannot find binding of given name: input_image原因TensorRT engine 的 binding name 与 C 代码中context-bindIndex(input_image)不一致。原始 SAM 的 ONNX 导出默认 binding name 是0、1等数字而非语义名。解决在how_to_export_vim_h_model.ipynb的导出代码中必须显式设置input_names[input_image, input_points, input_labels]并在trtexec命令中加--onnx-inputsinput_image:1x3x1024x1024,input_points:1x10x2,input_labels:1x10。4.2 现象[E] [TRT] Assertion failed: mPlugin-getOutputDataType(0, inputTypes, nbInputs)原因自定义插件如WindowAttentionPlugin的getOutputDataType返回nvinfer1::DataType::kFLOAT但输入 tensor 是kHALFFP16 模式。TensorRT 要求插件输出类型必须与输入类型一致。解决在plugins/window_attention_plugin.cpp中getOutputDataType方法改为nvinfer1::DataType getOutputDataType(int outputIndex, const nvinfer1::DataType* inputTypes, int nbInputs) const override { return inputTypes[0]; // 强制跟随第一个输入类型 }4.3 现象cudaErrorMemoryAllocation在context-enqueueV3()时崩溃原因TensorRT engine 的maxBatchSize设为 1但 C 代码中buffers分配的显存大小按batchSize4计算sizeof(float) * 3 * 1024 * 1024 * 4导致cudaMalloc分配超限。解决main.cpp第 187 行allocateBuffers()中batchSize必须从engine-getMaxBatchSize()动态读取int batchSize engine-getMaxBatchSize(); size_t inputSize sizeof(float) * 3 * 1024 * 1024 * batchSize;4.4 现象分割掩码全是噪声IoU 0.1原因ONNX 导出时未冻结 batch norm 参数model.eval()后仍存在running_mean/running_var的更新导致 TRT 推理时 BN 层输出漂移。解决在export.py中导出前插入for module in model.modules(): if isinstance(module, torch.nn.BatchNorm2d): module.eval() # 强制冻结 BN4.5 现象cv::cuda::resize报Gpu API call error: invalid argument原因OpenCV CUDA 模块要求输入cv::cuda::GpuMat的step必须是 32 字节对齐但 TensorRT 输出的float*内存未对齐。解决sam_utils.h中copyToGpuMat函数改用cv::cuda::createContinuous创建对齐内存cv::cuda::GpuMat gpu_mask; gpu_mask cv::cuda::createContinuous(h, w, CV_32F); gpu_mask.upload(mask_data, stream); // mask_data 是 TRT 输出指针5. 从 .pt 到 .engine 的七步实操一个命令都不能少的端到端流水线这不是理论推演而是我每天在 CI/CD 流水线里跑的真实步骤。整个流程耗时约 12 分钟RTX 4090输出sam_vith.engine2.1 GB和sam_vith_fp16.engine1.3 GB。每一步的命令、参数含义、失败检查点都列在下面照着敲就能过。5.1 Step 1准备 PyTorch 环境并加载官方 SAM 权重conda create -n sam-trt python3.9 conda activate sam-trt pip install torch2.0.1cu118 torchvision0.15.2cu118 --extra-index-url https://download.pytorch.org/whl/cu118 pip install onnx onnxruntime-gpu opencv-python wget https://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth检查点python -c import torch; print(torch.cuda.is_available())必须输出True否则后续trtexec无法启用 GPU。5.2 Step 2运行how_to_export_vim_h_model.ipynb导出 ONNX在 Jupyter 中执行 notebook关键参数MODEL_PATH sam_vit_h_4b8939.pthONNX_PATH sam_vith.onnxOPSET_VERSION 17DYNAMIC_AXES {input_image: {0: batch}, output_masks: {0: batch}}检查点onnx.checker.check_model(onnx.load(sam_vith.onnx))不报错用netron打开确认输入节点名为input_image非0。5.3 Step 3用trtexec生成 FP32 enginetrtexec --onnxsam_vith.onnx \ --saveEnginesam_vith.engine \ --workspace4096 \ --fp32 \ --best \ --timingCacheFiletiming.cache \ --minShapesinput_image:1x3x1024x1024,input_points:1x10x2,input_labels:1x10 \ --optShapesinput_image:1x3x1024x1024,input_points:1x10x2,input_labels:1x10 \ --maxShapesinput_image:1x3x1024x1024,input_points:1x10x2,input_labels:1x10 \ --onnxInputProfileinput_image:1x3x1024x1024,input_points:1x10x2,input_labels:1x10参数说明--workspace4096指定 4096 MB 显存用于 kernel 优化--best启用 exhaustive tactic search--onnxInputProfile强制 TRT 使用 ONNX 中的 profile避免 shape mismatch。5.4 Step 4生成 FP16 engine推荐部署用trtexec --onnxsam_vith.onnx \ --saveEnginesam_vith_fp16.engine \ --workspace4096 \ --fp16 \ --best \ --timingCacheFiletiming.cache \ --minShapesinput_image:1x3x1024x1024,input_points:1x10x2,input_labels:1x10 \ --optShapesinput_image:1x3x1024x1024,input_points:1x10x2,input_labels:1x10 \ --maxShapesinput_image:1x3x1024x1024,input_points:1x10x2,input_labels:1x10 \ --onnxInputProfileinput_image:1x3x1024x1024,input_points:1x10x2,input_labels:1x10注意--fp16不等于--halfTRT 中--fp16启用混合精度--half是旧版参数已弃用。5.5 Step 5编译 C 工程mkdir build cd build cmake -D CMAKE_BUILD_TYPERelease \ -D TENSORRT_ROOT/opt/tensorrt \ -D CUDA_HOME/usr/local/cuda-11.8 \ -D OpenCV_DIR/usr/local/lib/cmake/opencv4 \ .. make -j$(nproc)检查点ldd sam_trt | grep tensorrt应显示libnvinfer.so.8 /opt/tensorrt/lib/libnvinfer.so.8。5.6 Step 6运行推理测试./sam_trt --enginesam_vith_fp16.engine \ --imagedata/truck.jpg \ --points[[500,300],[600,400]] \ --labels[1,1] \ --outputtruck_mask.png参数说明--points是 JSON 格式字符串坐标为(x,y)--labels中1表示 foreground0表示 background输出truck_mask.png是 uint8 二值掩码。5.7 Step 7验证掩码质量关键不要只看图片用iou.py脚本量化评估# iou.py import cv2, numpy as np gt cv2.imread(data/truck_gt.png, cv2.IMREAD_GRAYSCALE) 0 pred cv2.imread(truck_mask.png, cv2.IMREAD_GRAYSCALE) 0 intersection np.logical_and(gt, pred).sum() union np.logical_or(gt, pred).sum() print(fIoU: {intersection/union:.3f}) # 正常应 0.85血泪经验如果 IoU 0.7立刻检查sam_utils.h中resizeImage是否用了双线性插值必须用cv::INTER_AREA降采样cv::INTER_LINEAR会引入 aliasing。6. 进阶技巧如何用trtexec的 timing cache 加速后续 engine 生成以及为何--buildOnly比--saveEngine更可靠很多人以为trtexec生成 engine 是一次性工作其实它内部维护一个timing.cache文件记录每个 layer 在不同 GPU 上的最优 kernel 选择。这个 cache 可以跨模型复用大幅缩短后续编译时间。但官方文档没说清楚两个关键细节cache 的格式兼容性和--buildOnly的真实用途。6.1 Timing cache 的复用边界与迁移方法timing.cache不是二进制黑盒而是 Protocol Buffer 序列化文件。它的兼容性取决于三个字段gpuIdNVIDIA GPU 的 PCI bus ID如0000:01:00.0computeCapCUDA compute capability如80tensorrtVersionTRT 主版本号如8只要这三个字段匹配cache 就可复用。迁移方法# 在 A 卡RTX 4090上生成 cache 后 scp timing.cache userjetson:/home/user/sam/ # 在 Jetson Orin 上先修改 cache 中的 gpuId用 protoc 解析后替换 protoc --decode_raw timing.cache | sed s/0000:01:00.0/0000:02:00.0/g | protoc --encodenvinfer1::timing::Cache timing_orin.cache注意computeCap和tensorrtVersion必须完全一致否则 TRT 会忽略 cache 并重新搜索 tactic。6.2--buildOnly是生产环境的唯一安全选项trtexec --saveEngine会立即序列化 engine 到磁盘但若中途断电或显存不足生成的.engine文件可能损坏file sam_vith.engine显示data而非ELF。而--buildOnly只构建 engine 对象不保存配合--dumpProfile输出 tactic 选择日志再用 C 代码安全保存// main.cpp 片段 IHostMemory* serializedModel engine-serialize(); std::ofstream p(sam_vith_fp16.engine, std::ios::binary); p.write(reinterpret_castconst char*(serializedModel-data()), serializedModel-size()); serializedModel-destroy();这样能捕获p.fail()错误避免部署时加载损坏 engine。6.3 用trtexec --exportLayerInfo定位性能瓶颈当推理延迟超标时别盲目调--workspace先看 layer 级耗时trtexec --onnxsam_vith.onnx --fp16 --exportLayerInfolayer_profile.json打开layer_profile.json找timeMs最大的 layer。常见瓶颈Layer Name典型耗时优化方案ViT_Encoder/layer.0/attn/qkv_proj12.4 ms用--sparsity启用稀疏化MaskDecoder/upsample8.7 ms替换为torch.nn.Upsample(modebilinear)导出output_masks5.2 ms确认输出 tensor 未启用dynamic_shape从那以后我每次生成新 engine都强制走一遍--exportLayerInfo--dumpProfile再对比前次日志。不是为了炫技而是因为 SAM 的 ViT-H 有 32 层 transformer某一层的 kernel 选错整帧延迟就多 15ms——这对 30 FPS 的产线相机就是 0.5 帧丢弃。希望帮到你。本文还有配套的精品资源点击获取