news 2026/10/19 6:06:05

YOLOv7量化部署实战:PTQ/QAT与TensorRT C++工程全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
YOLOv7量化部署实战:PTQ/QAT与TensorRT C++工程全解析

简介:面向目标检测模型落地需求,这份资源提供YOLOv7在PTQ和QAT两条量化路径下的训练与TensorRT部署方案,推理端以C++实现,适合需要将模型压缩并部署到GPU实时推断场景的算法工程师与嵌入式开发者,也适合希望快速掌握量化部署流程的研究者。压缩包共134个文件,大小约35.14MB,其中Python脚本、YAML配置与Jupyter笔记本主要覆盖量化训练和实验调试过程,C++源码、CUDA kernel与头文件构成TensorRT推理工程,另有容器配置与CMake构建脚本及说明文档,便于快速搭建一致性复现环境。已有486人学习,内容从量化原理到工程实现均有涉及。包内的核心C++部署代码、CUDA kernel与日志模块相互配合,可复现完整推理管线;PTQ快速转换结合QAT训练补偿,有助于在精度与速度之间取得平衡,缩减从训练到部署的试错成本;代码注释对构建选项、输入输出处理等做了说明,便于二次定制。

1. 先说结论:这是一份能直接上机的 YOLOv7 量化部署工程

如果你手头有一个训练好的 YOLOv7 模型,想部署到 GPU 设备上做实时推理,大概率会遇到两个问题:显存不够、延迟超标。浮点模型在边缘卡上跑不动是常态,这时候 PTQ(训练后量化) 和 QAT(量化感知训练) 就是两条必经路径,而量化之后的模型还得有一层 C++ 和 TensorRT 的工程封装才能真正落地。这个项目包就是干这件事的:从 PTQ / QAT 的量化训练思路,到 TensorRT 引擎构建,再到完整的 C++ 推理管线——输入预处理、模型加载、GPU 推理、后处理 NMS 全部串起来。适合手里已经训过 YOLO 系列模型、想把模型压到低精度并接进实际业务的开发者;也适合第一次接触 TensorRT 部署、想找个可复现的工程模板的人。拿到的不是教学 Demo,而是一套能跑、能改、能换模型的完整工程代码。

2. 项目定位:量化训练与 TensorRT 部署的完整闭环

2.1 先搞清楚 PTQ 和 QAT 在这个工程里的角色

大部分人在部署时会有一个误区:以为量化只是把权重从 FP32 改成 INT8 那么简单。实际上量化是两个阶段的事——训练阶段决定模型的精度上限,部署阶段决定能不能把精度保住。PTQ 是在模型训练完成后直接对权重做量化,不需要重新训练,速度快,但遇到分布敏感的网络层时精度会有明显回落;QAT 是在训练过程中插入伪量化节点,让模型提前适应低精度的权重和激活值分布,精度保持得更好,代价是训练时间变长。

这个项目把两条路都覆盖了,所以你在工程里能看到两条链路:一条是「训练好的权重 → PTQ 校准 → 生成量化引擎」,适合快速验证;另一条是「QAT 微调 → 导出量化模型 → TensorRT 部署」,适合对精度要求高的上线场景。两者的产物是一样的——一个 TensorRT 可以加载的 engine 文件,区别在于量化过程中对模型分布的处理方式。

素材里有一个 Dockerfile,这个很关键。TensorRT 的依赖很重——CUDA、cuDNN、TensorRT 库、OpenCV——每一样版本不对都能让编译翻车。用 Docker 把环境固化下来,意味着任何人拉下来构建都能得到一致的编译环境。我一般会把宿主机上的 CUDA 版本和容器内的版本严格对齐,否则编译能过,运行时会报找不到 libcudart 的错。

2.2 工程目录结构与文件职责

压缩包里的文件组织是典型的「CMake 工程 + 可执行程序 + 自定义 CUDA 核函数」结构,看一遍文件清单就能知道作者想让你怎么组织代码:

文件职责
app_yolov7.cpp主程序入口,负责读取配置、初始化引擎、循环推理
yolo.cpp/yolo.hYOLOv7 模型核心封装,引擎加载、推理上下文管理
utils.cpp/utils.h图像预处理、结果解析、通用工具函数
logger.cpp/logging.hTensorRT 日志回调,隔离推理过程中的日志输出
sampleOptions.cpp命令行参数解析,控制输入输出路径、量化精度
common.cmakeCMake 公共配置,统一查找依赖库
kernel_function.cuCUDA 核函数,负责 GPU 端的前处理或后处理
Dockerfile构建部署环境的容器定义
.gitignoreGit 忽略规则

这个拆分思路很值得借鉴:主程序只管调度,模型封装单独一个类,工具函数抽到 utils,与 TensorRT API 相关的活动对象和日志处理单独隔离,CUDA 核函数独立存放在.cu文件里。这样当你需要把项目从 YOLOv7 换成其他模型时,只需要替换yolo.cpp里的引擎加载和输出解析逻辑,主程序几乎不用动。

2.3 为什么选 C++ 而不是 Python

素材的关键词是c++,这不是随口标的。推理阶段的性能差距就体现在这里。Python 的推理脚本在调用 TensorRT Python API 时,每个 API 调用都有解释器开销,前处理用 OpenCV Python 绑定也有拷贝开销。在一个实时视频流场景下,这些开销累积起来会让端到端延迟多出 10 到 20 毫秒。而 C++ 可以直接操作 GPU 显存指针、用 CUDA 核函数把前处理也搬到 GPU 上,数据从解码到网络输入全程不需要离开显存。

另外工程里有个kernel_function.cu,这个文件的存在说明作者在 GPU 端做了定制化处理,比如图像缩放、BGR 到 RGB 的转换、归一化、甚至 NMS。这些操作如果放在 CPU 上做,每一帧都要经历一次 H2D(Host to Device)拷贝,而融合到 CUDA 核函数里以后,只需要维护一个设备端 buffer,吞吐量差距在批量处理时非常明显。

3. 核心模块拆解:从图像输入到检测框输出的完整链路

3.1 app_yolov7.cpp:主程序如何组织一次推理

主程序的流程不复杂,但顺序很重要。一句话概括:初始化引擎 → 读图 → 预处理 → 推理 → 后处理 → 输出结果。实际工程里大致长这样:

#include "yolo.h" #include "utils.h" #include "logging.h" #include "sampleOptions.h" int main(int argc, char** argv) { // 1. 解析命令行参数 Options options; if (!parseArgs(argc, argv, options)) { printUsage(); return -1; } // 2. 初始化 Logger 与 YOLO 推理实例 Logger gLogger; YOLO yolo(options, gLogger); // 3. 加载 TensorRT 引擎 if (!yolo.loadEngine()) { LOG_ERROR("Failed to load engine."); return -1; } // 4. 循环读取图像并推理 for (const auto& path : options.imagePathList) { cv::Mat image = cv::imread(path); if (image.empty()) { LOG_WARNING("Failed to read image: " + path); continue; } // 预处理 + 推理 + 后处理 auto detections = yolo.detect(image); // 绘制并保存结果 visualizeAndSave(image, detections, options.outputDir); } return 0; }

前两步好理解,关键在第三、四步。yolo.loadEngine()不是简单的文件读取,它要判断本地有没有序列化好的 engine 文件,有就直接反序列化加载,没有就调用 TensorRT API 重新构建。构建过程要经过 ONNX 解析、网络优化、层融合、精度校准(如果启用了 INT8/PTQ)等操作,耗时可能达到几十秒甚至几分钟,所以成品工程惯例是「构建一次,序列化保存,以后直接加载」。

yolo.detect(image)是推理全流程的封装:内部完成图像预处理、绑定输入输出 buffer、执行推理、解析输出、NMS 后处理。这一段是后续二次开发的重点关注区域,因为不同的 YOLO 版本、不同训练框架导出的模型,输出张量的形状和排列顺序可能不一样。

3.2 yolo.cpp:引擎加载与推理上下文的细节

这个文件是全工程最核心的封装,几个关键动作值得逐段展开。

bool YOLO::loadEngine() { // 1. 检查序列化引擎是否存在 if (fileExists(enginePath)) { std::ifstream file(enginePath, std::ios::binary); std::vector<char> data((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>()); runtime = createInferRuntime(gLogger); engine = runtime->deserializeCudaEngine(data.data(), data.size()); return engine != nullptr; } // 2. 不存在则从 ONNX 构建 auto builder = createInferBuilder(gLogger); auto network = builder->createNetworkV2(0); auto parser = nvonnxparser::createParser(*network, gLogger); if (!parser->parseFromFile(onnxPath.c_str())) { LOG_ERROR("Failed to parse ONNX file."); return false; } // 3. 设置构建配置 IBuilderConfig* config = builder->createBuilderConfig(); config->setMaxWorkspaceSize(1 << 30); if (precision == Precision::INT8) { // PTQ 路径:加载校准数据集 config->setFlag(BuilderFlag::kINT8); auto calibrator = new Int8Calibrator(calibrationDataPath); config->setInt8Calibrator(calibrator); } // 4. 构建并序列化 engine = builder->buildSerializedNetwork(*network, *config); std::ofstream file(enginePath, std::ios::binary); file.write(static_cast<char*>(engine->data()), engine->size()); return true; }

这段代码的关键点是第三个步骤里的setFlag(BuilderFlag::kINT8)。这是 PTQ 的核心——不设置这个标志,TensorRT 只会做 FP32 或 FP16 优化;设置之后,TensorRT 会对每一层做 INT8 量化,并通过校准器来收集激活值的分布范围。校准数据集的选择直接决定量化效果,后面避坑章节会专门讲。

构建完成后立即做序列化保存是必须的。buildSerializedNetwork返回的是一个序列化的 engine 字节流,你有两个选择:每次运行时重新构建,或者保存到磁盘下次直接加载。前者省事但每次都等几十秒,后者需要你自己管理enginePath文件的生命周期。工程里走的是第二种,这也是生产环境的常见做法。

推理上下文的创建同样值得注意,TensorRT 要求所有输入输出都通过ExecutionContext来管理:

bool YOLO::infer(const std::vector<float>& input, std::vector<float>& output) { // 绑定 GPU 显存 buffer void* buffers[kNumBindings]; cudaMalloc(&buffers[0], inputSize * sizeof(float)); cudaMalloc(&buffers[1], outputSize * sizeof(float)); // 拷贝预处理结果到 GPU cudaMemcpy(buffers[0], input.data(), inputSize * sizeof(float), cudaMemcpyHostToDevice); // 执行推理 context->enqueueV2(buffers, stream, nullptr); // 同步等待结果,拷贝回 CPU cudaStreamSynchronize(stream); cudaMemcpy(output.data(), buffers[1], outputSize * sizeof(float), cudaMemcpyDeviceToHost); // 别忘了释放显存 cudaFree(buffers[0]); cudaFree(buffers[1]); return true; }

enqueueV2要求传入一个数组指针,顺序对应网络输入的 binding 索引。这个索引不是按照 input/output 名字自然排列的,而是由 TensorRT 在构建引擎时决定的网络输入输出张量的顺序。如果你用engine->getBindingIndex("images")这类方式获取索引,会比写死数字更安全。

3.3 utils.cpp 与 kernel_function.cu:预处理和后处理的最佳实践

大多数第一次做部署的人都会在这里踩坑。YOLOv7 的预处理有几件事是固定的:resize 到模型输入尺寸、BGR 转 RGB、除以 255 归一化、HWC 转 CHW。如果这些操作全在 CPU 上用cv::resize和循环完成,一帧 640×640×3 的图像大概要多花 2 到 3 毫秒。看起来不多,但在 30 FPS 的流水线里就占掉了 10% 的时间预算。

工程里把这块用 CUDA 核函数做了融合,kernel_function.cu里一次完成整个预处理链路:

__global__ void preprocessKernel(const uchar3* src, float* dst, int srcW, int srcH, int dstW, int dstH) { int idx = blockIdx.x * blockDim.x + threadIdx.x; int total = dstW * dstH; if (idx >= total) return; int dx = idx % dstW; int dy = idx / dstW; // 计算源图像坐标,letterbox 保持宽高比 float scale = min((float)dstW / srcW, (float)dstH / srcH); float srcX = (dx - (dstW - srcW * scale) / 2) / scale; float srcY = (dy - (dstH - srcH * scale) / 2) / scale; if (srcX < 0 || srcX >= srcW || srcY < 0 || srcY >= srcH) { dst[(dy * dstW + dx) * 3 + 0] = 114.0f / 255.0f; dst[(dy * dstW + dx) * 3 + 1] = 114.0f / 255.0f; dst[(dy * dstW + dx) * 3 + 2] = 114.0f / 255.0f; return; } // 双线性采样 + BGR转RGB + 归一化 // ... 省略采样细节 }

这个核函数的输入输出设计是典型的 GPU 前处理结构:输入是uchar3类型的像素数组(节省显存),输出是float*且已经是 CHW 顺序、归一化好的数据,可以直接作为 TensorRT 的输入 buffer。关键是 letterbox 的处理——YOLOv7 推理时保持宽高比缩放,四周补灰边(114),如果不做这一步,检测框的坐标映射会全错。

后处理部分,NMS(非极大值抑制)是整个部署过程里最容易出错的地方。YOLOv7 的输出是一个维度类似[1, 25200, 85]的张量,其中 25200 是三个检测头的 anchor 总数,85 是 4 个框坐标 + 1 个目标置信度 + 80 个类别概率。后处理要做的事情包括:过滤置信度低的框、按类别做 NMS、把坐标从归一化值映射回原图尺寸。注意这里kernel_function.cu里通常会放一个 GPU 端的 NMS 实现,原因很简单——CPU 端对 25200 个框做两层循环,在视频流场景下就是性能瓶颈;用 GPU 并行做 topK 和抑制,可以将延迟从毫秒级降到微秒级。

3.4 数据流视角看整个链路

把上面几段串起来看,整个推理链路的数据流是很清晰的:

图像 → CPU 内存(cv::Mat)→ cudaMemcpy 到 GPU 显存 → CUDA 预处理核函数 → TensorRT 输入 buffer → TensorRT 推理 → 输出 buffer → 后处理核函数(置信度过滤 + NMS)→ 结果拷回 CPU → 绘制框并保存

这里面有一个值得留意的设计:预处理和后处理都在 GPU 上完成,中间的 TensorRT 引擎构建要保证输入输出 buffer 的绑定格式一致。具体到代码里,就是kernel_function.cu的输出布局必须和engine->getBindingDimensions(bindingIndex)完全对齐——包括通道顺序(CHW 还是 HWC)、归一化方式(除以 255 还是 0-1 归一化)、数据类型(float 还是 half)。这个一致性是部署环节最容易翻车的地方,后面避坑章会专门讲。

4. 编译与运行:从 Docker 到命令行推理

4.1 Dockerfile:把部署环境固化成基础设施

工程里的 Dockerfile 解决的是「环境玄学」问题——同一个代码,在我机器上编译能过,在你机器上就报undefined reference。深度学习部署的依赖链太长了:CUDA 驱动、CUDA Toolkit、cuDNN、TensorRT、OpenCV、CMake、GCC,任何一层版本偏了都会出问题。

合理的 Dockerfile 大致是这个思路:

FROM nvidia/cuda:11.7-cudnn8-devel-ubuntu20.04 # 安装依赖 RUN apt-get update && apt-get install -y \ libopencv-dev \ libssl-dev \ wget \ build-essential \ cmake # 安装 TensorRT RUN wget https://developer.nvidia.com/downloads/compute/machine-learning/tensorrt/secure/8.5.1/tars/TensorRT-8.5.1.7.Linux.x86_64-gnu.cuda-11.8.tar.gz \ && tar -xzf TensorRT-8.5.1.7.Linux.x86_64-gnu.cuda-11.8.tar.gz \ && cp -r TensorRT-8.5.1.7/include/* /usr/local/include/ \ && cp -r TensorRT-8.5.1.7/lib/* /usr/local/lib/ \ && cp -r TensorRT-8.5.1.7/lib/*.so* /usr/lib/x86_64-linux-gnu/ WORKDIR /workspace ADD . /workspace RUN mkdir build && cd build && cmake .. && make -j$(nproc)

要注意的是基础镜像选-devel版本而不是-runtime版本。runtime 镜像只包含运行所需的动态库,没有编译器、没有头文件、没有 CMake,编不了代码。devel 版本自带完整的 CUDA 工具链,编译链接一步到位。实际部署到目标机器时可以换成 runtime 镜像,把编译好的可执行文件放进去跑,这样镜像体积能小很多。

4.2 common.cmake:构建配置的要点

CMake 配置是这个工程里最容易被忽略但很重要的部分。common.cmake存在的意义是统一配置查找依赖库的逻辑,避免每个子目录的 CMakeLists.txt 重复写查找逻辑。典型的内容包括:

find_package(CUDA REQUIRED) find_package(OpenCV REQUIRED) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) # 查找 TensorRT set(TENSORRT_ROOT "/usr/local/tensorrt" CACHE PATH "Path to TensorRT") find_path(TENSORRT_INCLUDE_DIR NAMES NvInfer.h HINTS ${TENSORRT_ROOT}/include) find_library(TENSORRT_LIBRARY NAMES nvinfer HINTS ${TENSORRT_ROOT}/lib) # 编译 .cu 文件需要启用 CUDA 语言 enable_language(CUDA) cuda_add_library(preprocess_kernels STATIC kernel_function.cu)

如果你发现cmake ..之后找不到 TensorRT,大概率是TENSORRT_ROOT路径没指对,或者在容器里安装 TensorRT 时头文件和库文件被放到了系统目录之外。这种问题看 CMake 的报错信息就能定位,比运行时出错好排查得多。

4.3 命令行参数与运行示例

sampleOptions.cpp把整条推理链路中的可变参数全部暴露成命令行选项,处理得比较规整。常用参数大致有:

参数含义示例
--engine指定引擎文件路径--engine=yolov7_int8.engine
--onnx没有引擎时,指定 ONNX 来源--onnx=yolov7.onnx
--precision构建引擎时的精度--precision=int8
--calib_dataPTQ 校准图像目录--calib_data=./calib_images
--image推理输入图片--image=test.jpg
--output_dir结果输出目录--output_dir=./output

一次典型的 PTQ 流程从构建引擎开始:

mkdir -p build && cd build cmake .. && make -j$(nproc) # 第一步:从 ONNX 构建 INT8 引擎(会跑校准) ./app_yolov7 --onnx=yolov7.onnx --precision=int8 \ --calib_data=./calib_images \ --engine=yolov7_int8.engine # 第二步:用构建好的引擎推理 ./app_yolov7 --engine=yolov7_int8.engine \ --image=./test.jpg \ --output_dir=./output

第一次运行时控制台会密集输出 TensorRT 的每一层构建日志,包括层融合、精度分配、显存优化等信息,看起来像天书,但不用全部理解。你只需要关注最终是否显示Engine built successfully以及生成的 engine 文件大小。对于 640×640 的 YOLOv7 模型,INT8 引擎通常比 FP32 引擎体积小 3 到 4 倍。

验证是否跑对,有两个简单方法:一是看输出目录里的图片是否正确画出了检测框;二是对比 FP32 引擎和 INT8 引擎在同一张图上的检测结果,框的位置和类别应基本一致,mAP 损失在可接受范围内。

5. 避坑指南:量化部署最容易翻车的五个地方

5.1 现象:PTQ 之后精度掉得离谱,所有类别的 mAP 下降超过 10%

原因:校准数据集选得不对。PTQ 的校准过程是通过一组图像统计每一层激活值的分布范围,然后用这个范围来确定 INT8 的量化因子。如果你用的是从网上下载的通用图片做校准,而实际业务场景是夜间监控或者工业检测,激活值分布完全对不上,量化误差会被放大。

解决:校准数据集至少要覆盖真实业务场景的分布。常见做法是直接从验证集抽 500 到 1000 张有代表性的图,覆盖不同光照、不同角度、不同目标大小。如果业务场景是视频流,直接从视频里抽帧更靠谱。校准图片数量不是越多越好,500 张左右通常就能得到稳定的分布统计。

5.2 现象:engine 文件在 A 机器上构建成功,拷到 B 机器上加载报错

原因:TensorRT 的 engine 是与 GPU 架构绑定的,sm_86(Ampere 架构)上构建的 engine 不能直接跑到sm_75(Turing 架构)上。这和 ONNX 的跨平台特性完全不同,很多人第一次接触 TensorRT 都会在这个地方翻车。

解决:在每台目标机器上各构建一次,或者用 Dockerfile 里的基础镜像为特定 GPU 架构构建。如果目标机器显卡型号多,构建脚本最好按机型参数化,不要试图做一个「通吃」的 engine 文件。

5.3 现象:推理结果里所有检测框的位置都偏了,而且偏移量呈规律性

原因:预处理里的 letterbox 参数和模型训练时不一致。如果你训练时用的是 640×640 直接拉伸(没有保持宽高比),但部署代码里默认走 letterbox 补边,坐标映射出来就整体偏移。反过来,训练时用了 mosaic 增强和 letterbox,部署时直接cv::resize拉伸,也会错。

解决:部署前先确认训练时预处理的具体方式。看模型训练的配置文件里有没有 letterbox、padding 的数值是多少、归一化是除以 255 还是减均值除方差。部署时的预处理要和训练完全对齐,这个对齐是全工程最重要的细节,没有之一。

5.4 现象:CPU 后处理耗时接近 GPU 推理耗时,帧率上不去

原因:后处理还在 CPU 上跑。GPU 推理只需要几毫秒,但你在 CPU 上做了两层嵌套循环遍历 25200 个候选框做 NMS,每帧花掉十几毫秒,整个管线就卡在这了。

解决:后处理前移。置信度过滤可以用一个简单的 CUDA 核函数做并行筛选,NMS 可以用 TensorRT 内置的 EfficientNMS 插件,也可以自己实现 GPU 版本。工程里单独提供了kernel_function.cu,就是为此准备的。如果图省事,至少把置信度过滤放到 GPU 上,能滤掉 95% 以上的框,CPU 端再跑 NMS 压力就小很多。

5.5 现象:QAT 训练完导出 ONNX 后,转 TensorRT 时提示不支持的算子

原因:检测头的部分算子在导出 ONNX 时没有被完整转换,比如某些自定义的grid生成逻辑、sigmoid和exp的融合方式,或者输出层用了动态 shape。TensorRT 对标量运算的支持是有限的,遇到不认识的算子就直接报错。

解决:导出 ONNX 时把检测头的后处理部分留在模型外。YOLOv7 的模型结构里,推理时的 decode 逻辑(anchor 解码、置信度解码)可以留到 TensorRT 之后的 C++ 代码里做,ONNX 只保留 backbone + neck + head 的原始输出。这样既能过 TensorRT 解析,又让后处理逻辑变得可控、可调试。

6. 二次开发:把这份工程改成你自己的模型部署

6.1 换模型:从 YOLOv7 换到 YOLOv8 或自定义检测头

换模型时改动最集中的是yolo.cpp里的输出解析。YOLOv7 的输出是[1, 25200, 85]的二维形态,YOLOv8 的检测头结构不同,输出可能是[1, 84, 8400]的形态,且解码逻辑有差异。你需要调整的只有三处:输出张量的维度信息、解码公式、anchor 的处理方式。主程序和预处理逻辑基本不用动。

如果换了类别数,记得同步修改类别名称列表。比如从 COCO 的 80 类改成你自己训练的 5 类,这个列表定位在utils.cpp的classNames数组里,改完后绘制和过滤逻辑都会跟着生效。

6.2 按业务场景调参:输入分辨率、置信度阈值、NMS 阈值

输入分辨率是个典型的「精度与速度交换」旋钮。yolo.cpp初始化时有一个inputW和inputH,默认 640×640。如果你的设备算力有限,改成 416×416 会让推理速度快一截,但小目标检测能力会减弱。改的时候注意 letterbox 逻辑和坐标映射是自动适应的,你不用动核函数,只需要重新构建引擎,因为这个尺寸信息会被固化进 engine 里。

置信度和 NMS 阈值在visualizeAndSave的前面有对应的过滤条件。业务上如果追求召回率,把 confidence 阈值调低到 0.25;如果追求精确率,调高到 0.5 以上。NMS 的 IoU 阈值控制重复框的抑制强度,默认 0.45,目标重叠较多的场景可以调到 0.5。

6.3 工程化收尾:多线程流水线与 D 2D 拷贝优化

如果你想把这套代码接进摄像头视频流,建议改为两线程流水线:线程 A 负责解码和预处理,线程 B 负责推理和后处理。kernel_function.cu里的预处理核函数天然支持批处理,改造成batch=4或batch=8的批量推理,吞吐量提升比单帧循环明显。输入输出 buffer 的申请不要每次推理都做一次cudaMalloc,在初始化阶段一次性分配,推理时循环复用,能省掉大量重复开销。

另一个细节是把预处理核函数的输出直接接到 TensorRT 的 device buffer 上,省掉一次 D2D 拷贝。做法是cudaMalloc一块显存,先让预处理核函数写入这块显存,再把这同一个指针作为enqueueV2的输入。这样数据从进入 GPU 到输出结果全程不落地 CPU,端到端延迟能再砍掉一截。

回到部署这件事本身,我现在的习惯是:任何一个模型要上机,先在 Docker 里把 FP32 跑通,再对比 INT8 的 mAP 和耗时,最后才进正式流程。这套工程最让我受益的地方是把量化部署从「黑匣子」变成了「白盒」——哪里影响精度、哪里影响速度、哪里可以替换,代码结构上都标得清楚。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 6:05:53

告别千人一面:手把手构建懂你的个性化AI助手

做AI应用这几年&#xff0c;我听到最多的吐槽就是“你这AI怎么越用越像个‘人工智障’&#xff1f;”用户不是嫌它笨&#xff0c;而是嫌它千人一面——你问它吃什么&#xff0c;它永远给你一份万金油答案&#xff1b;你说自己海鲜过敏&#xff0c;它隔天又推荐虾仁沙拉&#xf…

作者头像 李华
网站建设 2026/10/19 6:05:31

递归与分治算法从入门到实战:核心思想、代码模板与栈溢出排查

带过几个刚学算法的同学&#xff0c;几乎每个人都会在递归这儿卡一阵子。画了半天递归树&#xff0c;程序一跑还是 stack overflow&#xff0c;整个人直接懵掉。其实递归和分治不是靠“看懂”学会的&#xff0c;是靠“拆开揉碎”练出来的。它们本质上是一件事&#xff1a;递归是…

作者头像 李华
网站建设 2026/10/19 6:04:26

Python高复用核心代码模板:配置、并发、重试与状态机实战

经常收到类似的问题&#xff1a;有没有那种“万能代码模板”&#xff0c;拿过来改改就能用&#xff1f;说实话&#xff0c;我以前挺排斥这个词&#xff0c;觉得万能意味着臃肿&#xff0c;但后来想通了&#xff0c;大家真正想要的不是全功能全家桶&#xff0c;而是一个30行左右…

作者头像 李华
网站建设 2026/10/19 6:03:00

曼哈顿距离在机器人路径规划中的原理、公式与Python实现

如果你让一台机器人从坐标(3,5)走到任务点(7,2)&#xff0c;它第一反应不是“直线飞过去”&#xff0c;而是问自己&#xff1a;我能不能走斜线&#xff1f;如果不能&#xff0c;那我该走多远&#xff1f;这时候曼哈顿距离就是最实在的答案。先别急着上代码&#xff0c;咱们先把…

作者头像 李华
网站建设 2026/10/19 6:02:56

专科生AI论文写作工具实测:千笔与知文使用体验对比

先说个背景。我的专科毕业论文&#xff0c;从开题到定稿一共折腾了四个月&#xff0c;其中有近两个月都在跟查重率和格式要求较劲。当时市面上能叫出名字的AI论文软件&#xff0c;我基本都试用过一轮&#xff0c;用得最久、最顺手的是两个——千笔写作工具和知文AI。这个对比不…

作者头像 李华