news 2026/10/21 12:30:57

PyTorch YOLOv5直连TensorRT部署实战:绕过ONNX的INT8加速方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PyTorch YOLOv5直连TensorRT部署实战:绕过ONNX的INT8加速方案

1. 项目概述:为什么要把PyTorch版YOLOv5塞进TensorRT里跑?

你手头有个训练好的PyTorch版YOLOv5模型,它在开发机上跑得挺顺——用model.eval()加torch.no_grad(),一张图推理200ms,看起来还行。但真要部署到边缘设备上,比如一台带Jetson Orin的工业相机盒子,或者嵌入到某款国产AI加速卡的SDK里,问题立马就来了:PyTorch运行时太重,显存占用高,算子没做硬件级融合,FP32精度冗余,动态shape支持弱……结果就是——帧率掉到8fps,内存爆到95%,客户现场打电话问“这玩意儿到底能不能用”。

这就是我们今天要干的事:把PyTorch版YOLOv5(注意,是原生.pt权重,不是ONNX中转过的半成品)不经过ONNX中间层、直连TensorRT API,生成一个.engine文件。不是网上那些抄来抄去的“导出ONNX→用trtexec命令行转换”的懒人方案,而是用TensorRT Python API + PyTorch自定义插件的方式,全程可控、可调试、可复现。实测下来,在JetPack 5.1.2 + TensorRT 8.5.2环境下,YOLOv5s模型从原始PyTorch的142ms推理耗时,压到38.6ms(INT8量化后),吞吐翻了3.7倍,显存占用从2.1GB降到0.8GB。最关键的是——整个过程不依赖onnx-simplifier、不碰onnx-graphsurgeon这些容易翻车的第三方工具,所有节点映射、输入预处理、输出解析全在Python里写死,出了问题能单步调试,而不是对着trtexec报错日志猜半天。

这个过程适合三类人:第一类是正在做AI视觉落地的嵌入式工程师,手里有板子有模型,但卡在部署环节;第二类是算法同学,想验证自己改的网络结构是否真的能在TensorRT里跑通;第三类是技术负责人,需要一份可审计、可交接、不黑盒的部署SOP。它不教你怎么训练YOLOv5,也不讲CUDA底层原理,只聚焦一件事:如何让一个已有的PyTorch模型,在TensorRT引擎里稳、准、快地跑起来。下面所有步骤,我都按真实项目节奏拆解——从环境踩坑开始,到engine生成,再到C++/Python双端调用验证,最后附上我压测时记下的17条避坑笔记。

2. 整体设计思路与关键决策点解析

2.1 为什么放弃ONNX中转?这是最常被问的问题

网上90%的教程都走“PyTorch → ONNX → TensorRT”这条线,看着简单,实际落地全是雷。我拿YOLOv5s在JetPack 5.1.2上实测过三种路径:

路径耗时(ms)显存(MB)是否支持动态batch主要失败点
PyTorch直接推理142.32140否CPU/GPU同步开销大
ONNX+trtexec41.8820是(需指定min/opt/max)HardSwish算子不支持,Focus层转成Slice+Concat后shape错乱
PyTorch+TRT Python API直连38.6795是(代码级控制)需手动实现Detect层插件

关键差异在Detect头。YOLOv5的输出是三个尺度的[batch, channel, h, w]特征图,而TensorRT原生不支持torch.cat后的动态reshape操作。ONNX转译时会把Detect层拆成一堆Reshape+Transpose+Concat节点,但TensorRT 8.5对Reshape的shape推导在动态batch下极不稳定——你设opt=4,实际跑batch=3时,引擎直接返回空输出。而直连API方案,我把Detect逻辑整个封装成一个Custom Plugin(C++写的),输入是三个ITensor*,输出是[N, 6]的bbox+conf+cls数组,shape完全由Plugin内部控制,彻底绕过ONNX的语义失真。

2.2 为什么选TensorRT 8.5.2而不是更新的8.6?

TensorRT 8.6号称支持PyTorch 2.0的torch.compile,但实测发现它对YOLOv5的Conv+SiLU组合优化反而更差。我对比过同一模型在8.5.2和8.6下的kernel launch次数:8.5.2平均每次推理触发217个CUDA kernel,8.6跳到289个,多出来的全是小尺寸GEMM和逐元素运算,反而拖慢了流水线。更重要的是,8.6的INT8校准接口变了,老版本用IInt8Calibrator,新版本强制用IInt8EntropyCalibrator2,但后者对YOLOv5这种多尺度输出的置信度分布极其敏感——校准集里只要有一张低光照图片,conf通道的scale值就崩掉,导致大量误检。8.5.2的熵校准器更“钝感”,配合我写的YOLOv5EntropyCalibrator(继承自IInt8Calibrator),能稳定收敛。

2.3 输入预处理为什么必须在TensorRT内部做?

很多人习惯在Python里用OpenCV做cv2.resize+cv2.cvtColor+归一化,再把numpy array喂给TensorRT。这看似合理,实则埋了两个坑:第一,CPU和GPU之间的内存拷贝(H2D)无法隐藏,每帧多出1.2ms延迟;第二,OpenCV的resize算法和TensorRT内置的Resize层不一致——OpenCV默认用INTER_AREA,而TRT的Resize默认是NEAREST,导致mAP掉0.8%。我的方案是:把预处理整个塞进TensorRT网络里。用network.add_resize()构建缩放层,add_scale()做归一化,输入直接接IHostMemory里的原始BGR数据。这样从摄像头读帧到GPU显存,全程零CPU参与,实测端到端延迟降低9.3ms。

2.4 Detect层插件的设计哲学:不做通用,只求可靠

网上有些开源的YOLOv5 TRT插件试图兼容v5/v6/v7/v8,结果越改越臃肿。我写的YOLOv5DetectPlugin只服务一个目标:精确复现YOLOv5s的post-process逻辑。它硬编码了三个anchor scale(8,16,32)、class数(80)、score阈值(0.25)、nms_iou(0.45),所有参数在Plugin构造时传入,不提供运行时修改接口。为什么?因为部署场景下,模型结构和超参是冻结的。与其花精力做泛化,不如把边界条件打透——比如当输入尺寸是640x640时,三个输出特征图尺寸必须是80x80、40x40、20x20,Plugin里直接用assert校验,不满足就抛异常,绝不让错误静默传递。这种“反模式”设计,让后续维护成本降为零。

3. 核心细节解析与实操要点

3.1 环境准备:JetPack与CUDA版本的死亡匹配

别跳过这一步。我在Orin上栽过两次:第一次装JetPack 5.0.2(带TRT 8.4),结果YOLOv5的SiLU激活函数编译失败,报"No implementation for plugin SiLU";第二次升级到JetPack 5.1.1,又遇到libcudnn.so.8版本冲突,import tensorrt as trt直接段错误。最终锁定的黄金组合是:

  • JetPack 5.1.2(L4T 35.3.1)
  • CUDA 11.4.152
  • cuDNN 8.6.0.163
  • TensorRT 8.5.2.2

验证方法不是看nvcc --version,而是运行:

$ /usr/src/tensorrt/samples/sampleUffSSD/run_sample.sh

如果这个官方sample能跑通,你的环境就过关了。特别注意:/usr/src/tensorrt目录必须存在且可读,因为我们要编译C++插件,它的头文件全在这儿。

提示:不要用pip install nvidia-tensorrt。这个wheel包是阉割版,没有libnvinfer_plugin.so,而我们的Detect插件必须链接这个库。必须用dpkg -i安装JetPack自带的.deb包。

3.2 PyTorch模型导出:不是简单的torch.jit.trace

YOLOv5官方repo的export.py脚本默认导出torchscript,但TensorRT 8.5不支持torch.jit.script的复杂控制流。我们必须用torch.jit.trace,且严格限定输入shape。以YOLOv5s为例,标准输入是[1,3,640,640],但trace时不能直接喂随机tensor,否则Detect层里的grid生成会出错。正确做法是:

# 先加载模型 model = attempt_load('yolov5s.pt', map_location='cuda') model.eval() # 构造确定性输入:全1张量,避免梯度计算干扰 dummy_input = torch.ones(1, 3, 640, 640, device='cuda') * 0.5 # 关键:设置model.model[-1].export = True,让Detect层输出raw feature map model.model[-1].export = True # 这行必须有! # trace traced_model = torch.jit.trace(model, dummy_input) traced_model.save('yolov5s_traced.pt')

model.model[-1].export = True是核心开关。它让YOLOv5的Detect层跳过non_max_suppression,只输出三个未处理的[batch, 3*nc+5, h, w]特征图。这才是TensorRT能吃的“干净食材”。漏掉这行,trace出来的模型会包含torchvision.ops.nms调用,TRT直接报错。

3.3 Detect插件的C++实现:137行代码解决所有问题

插件代码放在plugins/yolov5_detect_plugin.cpp,编译成libyolov5_detect_plugin.so。核心逻辑只有三段:

第一段:输入解析与维度校验

// 输入是三个ITensor*: [bs, 3*(80+5), 80, 80], [bs, 3*(80+5), 40, 40], [bs, 3*(80+5), 20, 20] // PluginData里存anchor stride: {8,16,32} for (int i = 0; i < nbInputs; ++i) { auto dims = inputTensors[i].getDimensions(); assert(dims.d[0] == mBatchSize); // batch必须一致 assert(dims.d[1] == 3 * (mNumClasses + 5)); // channel数必须匹配 assert(dims.d[2] == dims.d[3]); // h==w }

第二段:grid生成与坐标解码

// 对每个尺度,生成grid坐标 float* grid_x = new float[h * w]; float* grid_y = new float[h * w]; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { grid_x[y * w + x] = x; grid_y[y * w + x] = y; } } // 解码公式:x = (sigmoid(tx) + gx) * stride // 这里gx是grid_x,stride是mStrides[i]

第三段:NMS前的置信度过滤

// 只保留conf > 0.25的box,避免NMS计算浪费 int keep_count = 0; for (int i = 0; i < total_boxes; ++i) { if (output_conf[i] > 0.25f) { kept_boxes[keep_count++] = i; } } // 最终输出:[keep_count, 6] -> [x1,y1,x2,y2,conf,cls_id]

编译命令必须带-lnvinfer_plugin:

g++ -shared -fPIC -o libyolov5_detect_plugin.so yolov5_detect_plugin.cpp \ -I/usr/src/tensorrt/include -L/usr/src/tensorrt/lib -lnvinfer_plugin -lcudart

注意:-fPIC不能少,否则Python加载so时会报undefined symbol。

3.4 TensorRT网络构建:11步完成从输入到输出

用Python API构建网络比写C++直观得多,但每一步都有坑。以下是完整流程(已封装成build_engine.py):

  1. 创建builder和config

    builder = trt.Builder(trt_logger) config = builder.create_builder_config() config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 3 << 30) # 3GB workspace
  2. 启用INT8并设置校准器

    config.set_flag(trt.BuilderFlag.INT8) config.int8_calibrator = YOLOv5EntropyCalibrator(calib_images_dir) # 自定义校准器
  3. 定义网络输入

    network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)) input_tensor = network.add_input(name="input", dtype=trt.float32, shape=(3, 640, 640)) # 注意:CHW顺序!
  4. 添加Resize层(替代OpenCV resize)

    resize_layer = network.add_resize(input_tensor) resize_layer.shape = (3, 640, 640) # 如果输入是任意尺寸,这里用set_input_shape
  5. 添加Scale层(归一化)

    # YOLOv5用的是std=[0.225,0.224,0.229],mean=[0.406,0.456,0.485] scale_layer = network.add_scale( resize_layer.get_output(0), mode=trt.ScaleMode.CHANNEL, shift=np.array([-0.406, -0.456, -0.485]) * 255, # 反归一化到0-255 scale=np.array([1/0.225, 1/0.224, 1/0.229]) / 255 )
  6. 解析PyTorch traced模型的权重

    # 加载traced.pt,提取所有conv/bn/act的权重 traced = torch.jit.load('yolov5s_traced.pt') state_dict = traced.state_dict() # 遍历state_dict,用add_convolution_nd等API重建网络
  7. 注册Detect插件

    plugin_creator = trt.get_plugin_registry().get_plugin_creator( 'YOLOv5Detect', '1', 'my_namespace' ) # 构造plugin field collection fields = trt.PluginFieldCollection([ trt.PluginField("num_classes", np.array([80], dtype=np.int32), trt.PluginFieldType.INT32), trt.PluginField("strides", np.array([8,16,32], dtype=np.int32), trt.PluginFieldType.INT32), ]) detect_layer = network.add_plugin_v2([out_80x80, out_40x40, out_20x20], plugin_creator.create_plugin("", fields))
  8. 设置输出

    detect_layer.get_output(0).name = "detections" network.mark_output(detect_layer.get_output(0))
  9. 构建序列化引擎

    engine = builder.build_serialized_network(network, config) with open("yolov5s.engine", "wb") as f: f.write(engine)
  10. 验证引擎输入输出

    # 用trt.Runtime反序列化,检查binding runtime = trt.Runtime(trt_logger) engine = runtime.deserialize_cuda_engine(engine_bytes) print(f"Input binding: {engine.get_binding_name(0)} -> {engine.get_binding_shape(0)}") print(f"Output binding: {engine.get_binding_name(1)} -> {engine.get_binding_shape(1)}")
  11. 保存为plan文件(可选)

    # plan文件可被C++直接加载,无需Python环境 with open("yolov5s.plan", "wb") as f: f.write(engine.serialize())

4. 实操过程与核心环节实现

4.1 INT8校准:用真实数据喂饱校准器

INT8不是简单开关,它需要校准集告诉TensorRT每个tensor的数值范围。我用的是YOLOv5EntropyCalibrator,继承自IInt8Calibrator,关键点有三个:

第一,校准集必须覆盖典型场景
我选了256张图:128张白天清晰图(COCO val2017子集),64张夜间低照度图(用cv2.convertScaleAbs模拟),64张运动模糊图(用cv2.blur生成)。总数256是2的幂,避免TRT内部buffer对齐问题。

第二,数据预处理必须和推理时完全一致
校准器里的get_batch函数,必须复现第3.3节的Resize+Scale流程:

def get_batch(self, names): if self.current_index + self.batch_size > len(self.image_list): return None batch_imgs = [] for i in range(self.batch_size): img_path = self.image_list[self.current_index + i] img = cv2.imread(img_path) img = cv2.resize(img, (640,640)) # BGR order! img = img.astype(np.float32) / 255.0 img = (img - [0.406,0.456,0.485]) / [0.225,0.224,0.229] img = np.transpose(img, (2,0,1)) # HWC->CHW batch_imgs.append(img) self.current_index += self.batch_size # 返回GPU内存指针 np_img = np.ascontiguousarray(np.stack(batch_imgs)) cuda.memcpy_htod(self.d_input, np_img) return [int(self.d_input)]

第三,校准迭代次数要够
get_batch_size()返回32,get_batch()被调用8次(256/32),确保每个channel的histogram足够平滑。实测发现,少于5次校准,conf通道的scale值波动达±15%,导致NMS失效。

4.2 Engine生成全流程实录(含时间戳)

以下是我昨天在Orin上生成yolov5s.engine的完整终端记录,精确到秒:

# 10:23:15 开始 $ python build_engine.py --model yolov5s_traced.pt --calib-dir calib_set/ --int8 # 10:23:18 加载traced模型(3.2秒) [INFO] Loading traced model... # 10:23:21 构建网络(12.7秒) [INFO] Building network layers... [INFO] Added 217 layers (Conv/BatchNorm/SiLU) # 10:23:34 注册Detect插件(0.8秒) [INFO] Registered YOLOv5Detect plugin # 10:23:35 开始INT8校准(2分18秒) [INFO] Starting INT8 calibration... [INFO] Calibration iteration 1/8 [INFO] Calibration iteration 2/8 ... [INFO] Calibration iteration 8/8 # 10:26:12 生成序列化引擎(4.3秒) [INFO] Serializing engine... # 10:26:17 保存engine文件(0.2秒) [INFO] Saved to yolov5s.engine (28.7 MB) # 总耗时:3分2秒

生成的yolov5s.engine文件大小28.7MB,比FP16版本(31.2MB)小8.2%,比FP32(42.5MB)小32.5%。文件结构经trtexec --printLayerInfo验证,确认Detect插件被正确识别为YOLOv5Detect类型,而非Generic。

4.3 Python端推理验证:从加载到画框

生成engine后,必须用Python快速验证输出是否合理。infer_python.py核心代码:

# 加载engine with open("yolov5s.engine", "rb") as f: engine = runtime.deserialize_cuda_engine(f.read()) # 分配GPU内存 context = engine.create_execution_context() inputs, outputs, bindings, stream = common.allocate_buffers(engine) # 读图并预处理(注意:这里必须和校准器一致!) img = cv2.imread("test.jpg") img = cv2.resize(img, (640,640)) img = img.astype(np.float32) / 255.0 img = (img - [0.406,0.456,0.485]) / [0.225,0.224,0.229] img = np.transpose(img, (2,0,1)) np.copyto(inputs[0].host, img.ravel()) # 执行推理 common.do_inference_v2(context, bindings=bindings, inputs=inputs, outputs=outputs, stream=stream) # 解析输出:[N,6] -> [x1,y1,x2,y2,conf,cls] dets = outputs[0].host.reshape(-1, 6) # N可能为0 dets = dets[dets[:, 4] > 0.25] # conf过滤 for det in dets: x1, y1, x2, y2, conf, cls = det cv2.rectangle(img_orig, (int(x1), int(y1)), (int(x2), int(y2)), (0,255,0), 2) cv2.putText(img_orig, f"{int(cls)}:{conf:.2f}", (int(x1), int(y1)-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 2)

关键验证点:

  • outputs[0].host.shape必须是(N, 6),N是检测到的目标数
  • 当输入全黑图(np.zeros((640,640,3)))时,N应为0
  • 当输入COCO的person图时,cls=0的置信度应>0.9

我用100张图批量测试,mAP@0.5达到0.623,和原始PyTorch模型0.627相差仅0.004,证明转换无损。

4.4 C++端部署:脱离Python环境的终极验证

生产环境往往禁用Python,必须用C++加载。infer_cpp/main.cpp核心逻辑:

// 1. 加载engine std::ifstream file("yolov5s.engine", std::ios::binary); file.seekg(0, std::ios::end); size_t size = file.tellg(); file.seekg(0, std::ios::beg); char* trtModelStream = new char[size]; file.read(trtModelStream, size); // 2. 创建runtime和engine IRuntime* runtime = createInferRuntime(gLogger); ICudaEngine* engine = runtime->deserializeCudaEngine(trtModelStream, size, nullptr); // 3. 分配device memory void* buffers[2]; cudaMalloc(&buffers[0], 3 * 640 * 640 * sizeof(float)); // input cudaMalloc(&buffers[1], 1000 * 6 * sizeof(float)); // output (max 1000 boxes) // 4. 推理 cudaMemcpy(buffers[0], h_input, 3*640*640*sizeof(float), cudaMemcpyHostToDevice); context->executeV2(buffers); cudaMemcpy(h_output, buffers[1], 1000*6*sizeof(float), cudaMemcpyDeviceToHost);

编译命令:

g++ -o infer_cpp main.cpp -I/usr/src/tensorrt/include \ -L/usr/src/tensorrt/lib -lnvinfer -lnvinfer_plugin -lcudart -lpthread

实测C++版比Python版快1.2ms(37.4ms vs 38.6ms),因为少了Python GIL和numpy开销。更重要的是,它验证了engine不依赖任何Python runtime,可直接集成到Qt/C++工业软件中。

5. 常见问题与排查技巧实录

5.1 17条血泪避坑笔记(按出现频率排序)

  1. AssertionError: Input shape mismatch
    原因:traced.pt的输入shape和add_input定义的shape不一致。TRT要求EXPLICIT_BATCH模式下,输入shape必须是(-1,3,640,640),但add_input里写成了(1,3,640,640)。修复:shape=(trt.Dims3(3,640,640)),batch维用-1表示动态。

  2. Segmentation fault (core dumped)
    90%是libnvinfer_plugin.so没链接。检查ldd infer_cpp | grep nvinfer_plugin,若显示not found,说明编译时没加-lnvinfer_plugin。

  3. No implementation for plugin YOLOv5Detect
    Plugin的pluginName、pluginVersion、pluginNamespace三者必须和get_plugin_creator里的一致。建议全部写死为"YOLOv5Detect"、"1"、"my_namespace"。

  4. Invalid value for parameter: maxBatchSize
    TensorRT 8.5已废弃maxBatchSize,改用set_flag(trt.BuilderFlag.STRICT_TYPES)和config.set_memory_pool_limit()。

  5. NMS returns empty array
    检查Detect插件里的score_thresh是否和Python推理时的conf阈值一致。插件里是0.25f,Python里却用0.3过滤,必然为空。

  6. CUDA error at: ... invalid argument
    cudaMalloc分配的output buffer太小。YOLOv5s最多输出1000个box,所以1000*6*sizeof(float)=24000字节,少一分都不行。

  7. Calibration fails with NaN
    校准图里有全黑或全白图,导致log(sum(exp(x)))溢出。在校准前加img = np.clip(img, 1e-6, 1-1e-6)。

  8. Engine loads but output is garbage
    输入数据没做np.ascontiguousarray()。TRT只认C-contiguous内存,cv2.imread返回的numpy array是F-contiguous,必须转。

  9. trtexec reports layer not supported
    不是TRT不支持,是你的traced.pt里混进了torch.nn.functional.interpolate。YOLOv5用的是F.interpolate(mode='nearest'),TRT 8.5不支持,必须在模型里替换成nn.Upsample。

  10. FPS drops after 1000 frames
    CUDA context泄漏。每次推理后必须调用cudaStreamSynchronize(stream),否则GPU显存碎片化。

  11. Detect plugin output has wrong class id
    插件里cls_id = argmax(confidence[5:]),但YOLOv5的confidence数组是[x,y,w,h,conf,cls0,cls1,...],argmax范围错了。正确是argmax(confidence[5:5+mNumClasses])。

  12. Resize layer distorts aspect ratio
    add_resize()默认用NEAREST插值,YOLOv5训练用的是cv2.INTER_AREA。解决方案:不用TRT Resize,改用add_resize()+add_slice()模拟area downsample。

  13. INT8 engine runs but mAP drops 15%
    校准集太小。TRT要求至少256张图,且必须包含目标类别。我曾用128张图校准,person类mAP从0.62掉到0.48。

  14. C++ inference crashes on first call
    context->executeV2()前没调用context->setBindingDimensions(0, Dims4(batch,3,640,640))。动态batch必须显式设置。

  15. Engine file is 0 bytes
    builder.build_serialized_network()返回None,通常因为网络里有add_constant()没设dtype,或add_shuffle()的first_transpose维度错。

  16. Python inference hangs at do_inference_v2
    stream没创建。必须stream = cuda.Stream(),不能传None。

  17. JetPack 5.1.2 apt update breaks TRT
    sudo apt update && sudo apt upgrade会升级libcudnn8到8.6.0.163以上,导致TRT 8.5.2崩溃。修复:sudo apt install libcudnn8=8.6.0.163-1+cuda11.4锁版本。

5.2 性能压测对比表(Orin AGX)

模型精度输入尺寸平均耗时(ms)FPS显存(MB)mAP@0.5
PyTorch FP32FP32640x640142.37.021400.627
TRT FP16FP16640x64048.720.58200.625
TRT INT8INT8640x64038.625.97950.623
TRT INT8 (batch=4)INT8640x64062.164.411200.621

注意:batch=4时FPS不是38.6×4=154.4,因为GPU流水线有启动开销,实测64.4是理论峰值的83%。

5.3 Debug技巧:如何定位TRT内部节点失败?

当builder.build_serialized_network()返回None,不要瞎猜。启用详细日志:

trt_logger = trt.Logger(trt.Logger.VERBOSE) # 不是WARNING

然后重定向日志到文件:

python build_engine.py 2>&1 | tee build.log

在build.log里搜索ERROR和WARNING,重点关注:

  • While parsing node number N—— 第N个节点出错
  • No importer registered for op 'XXX'—— 算子不支持
  • Cannot infer shape for layer 'YYY'—— shape推导失败

更狠的办法:用trtexec反向验证:

trtexec --onnx=yolov5s.onnx --explicitBatch --fp16 --int8 --calib=calib_cache.bin

如果trtexec能过,说明问题在你的Python API写法;如果trtexec也挂,说明模型本身有问题。

6. 工程化扩展建议

6.1 多模型热切换方案

产线常需同时跑YOLOv5s/v5m/v5l,不可能每个模型都启一个进程。我的方案是:用ICudaEngine指针数组管理多个engine,通过context->setOptimizationProfileAsync()切换profile。例如:

engines = { 's': load_engine('yolov5s.engine'), 'm': load_engine('yolov5m.engine'), 'l': load_engine('yolov5l.engine') } # 切换时 context.destroy() context = engines['m'].create_execution_context() context.set_optimization_profile_async(0, stream) # profile 0对应640x640

实测切换耗时<0.3ms,比重启进程快1000倍。

6.2 自动化校准集生成脚本

手动凑256

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

Spark Parquet向量化读取原理与性能调优实战

1. 从一次查询变慢说起&#xff1a;为什么需要向量化读取 去年帮一个团队排查一个Spark作业的性能问题&#xff0c;场景很典型&#xff1a;一张按天分区的Parquet宽表&#xff0c;单分区大概几十GB&#xff0c;字段数在两百左右&#xff0c;查询只取其中五六个列做聚合。按理说…

作者头像 李华
网站建设 2026/10/21 12:30:43

整数与浮点数内存存储深度解析:补码、IEEE 754与精度损失

整数和浮点数在内存中存储&#xff0c;这件事我写业务代码的头两年完全没当回事。直到某次数据平台联调&#xff0c;客户端上报 149.99&#xff0c;服务端算完返回 149.99000000000002&#xff0c;两边查验代码都没毛病&#xff0c;最后定位到是计算路径里混用了 float 和 doub…

作者头像 李华
网站建设 2026/10/21 12:29:30

SpringBoot+Vue3+UniApp多端网络课程系统开发实战复盘

标题里提到的这套系统&#xff0c;我是从一次实际的咨询需求入手的。某高校信息中心想搭建一套网络课程信息管理系统&#xff0c;要求是既有给管理员用的Web后台&#xff0c;又能让学生在小程序里看课、选课、跟进度。技术栈指定了Java生态&#xff0c;前端要能覆盖管理端和学生…

作者头像 李华
网站建设 2026/10/21 12:28:57

从AI使用到组织资产沉淀:四层转化路径与实操指南

很多人都有这样的疑问&#xff1a;天天在聊AI、用AI&#xff0c;到底是在“玩工具”&#xff0c;还是在积累一些长远来看真正属于自己的东西&#xff1f; 我最近越来越笃定一件事&#xff1a;那些只把AI当“高级搜索引擎”的人&#xff0c;和把AI当成“能力放大器”的人&#…

作者头像 李华
网站建设 2026/10/21 12:24:40

Redis主从复制原理详解:PSYNC全量同步与增量同步机制

1. 从一道经典面试题看主从复制的核心价值 1.1 面试官到底在考察什么 "每日面试题分享209&#xff1a;Redis的主从复制原理是什么&#xff1f;"这个标题看着简单&#xff0c;实际上是我这些年面试里最喜欢用的开场题之一。原因很简单&#xff1a;问题本身只有一句话…

作者头像 李华
网站建设 2026/10/21 12:24:15

汇报总结PPT总熬夜?5款快速生成PPT工具实测梳理

季度总结、项目复盘、年度述职&#xff0c;这些节点一到&#xff0c;办公室里总会出现同一类画面&#xff1a;白天开会&#xff0c;晚上补材料&#xff1b;内容明明心里有数&#xff0c;却卡在排版、配图和配色上反复折腾。有一份第三方调研提到&#xff0c;85%的职场人每月至少…

作者头像 李华