当前位置: 首页 > news >正文

ONNX模型多线程推理并解决线程踩踏与显存溢出问题

模型推理慢问题

在进行模型推理时,摄像头以30帧/秒的速度持续采集图像,而模型推理速度相对较慢,难以实时处理每一帧图像,从而导致丢帧现象。这种情况下,部分已完成的动作可能因对应帧被丢弃而未被识别。

多线程推理

为解决该问题,我们设计了图像存储队列,设计如下:
frame_queue(图像待处理队列),result_queue(图像处理结果队列),record_queue(记录队列)

每个队列的最大存储数量为10

同时要开启多个线程去进行模型推理,这里max_worker设置为4

#定义并开启推理线程self.process_thread=threading.Thread(target=self._process_loop,daemon=True)self.process_thread.start()#定义并开启记录线程self.record_thread=threading.Thread(target=self._record_loop,daemon=True)self.record_thread.start()
def_process_loop(self):"""主循环"""# 初始化线程池withThreadPoolExecutor(max_workers=self.max_workers)asself.executor:frame_id=0whileself.running:try:# --- 步骤 1: 从摄像头/输入源获取帧 ---ifnotself.frame_queue.empty():frame=self.frame_queue.get(timeout=0.1)frame_id+=1# --- 步骤 2: 将处理任务交给线程池 ---try:self.executor.submit(self._worker_task,frame,frame_id)exceptExceptionase:print(f"提交任务失败:{e}")# --- 步骤 3: 检查并获取处理结果 (非阻塞) ---try:whilenotself.result_queue.empty():res_id,annotated_frame=self.result_queue.get_nowait()# 更新显示帧withself.lock:self.current_annotated_frame=annotated_frame# 录像ifself.is_recordingandframeisnotNone:try:self.record_queue.put_nowait(frame)exceptqueue.Full:passexceptqueue.Empty:passtime.sleep(0.001)# 稍微让出CPUexceptExceptionase:print(f"主循环错误:{e}")continue

推理线程可同步执行

def_worker_task(self,frame,frame_id):"""线程池中执行的具体任务"""try:# 1. 执行算法处理annotated_frame=self._process_frame(frame)# 2. 将结果放入回传队列try:self.result_queue.put_nowait((frame_id,annotated_frame))exceptqueue.Full:pass# 如果结果队列满了,丢弃旧结果exceptExceptionase:print(f"处理线程出错:{e}")

记录线程实现如下,将记录功能单开一个线程,可以有效避免写入延迟

def_record_loop(self):"""录像线程:独立运行,只负责写入"""whileself.running:try:frame=self.record_queue.get(timeout=0.1)ifself.is_recordingandself.video_writerisnotNone:self.video_writer.write(frame)exceptqueue.Empty:time.sleep(0.01)exceptException:continue

当开启多线程推理后,显存占用与利用率对比如下:


通过任务管理器我们可以看到,CPU依旧坚挺

线程踩踏问题

通过这个方法,可以有效的解决推理延迟导致的丢帧问题,然而,正当我满心欢喜时,发现我保存的图像出现了乱码现象,如下图所示:

线程踩踏问题解决方法

这是由于我开启了多线程,每个线程中的模型均检测到了对应动作,此时同时进行了图像写入操作导致的,这是多线程环境下的经典“踩踏”问题:多个线程同时检测通过,然后一起挤向cv2.imwrite,结果就是文件被覆盖、路径冲突,甚至程序崩溃。

要解决这个问题,核心思路就一个:让“写文件”这件事变成“单行道”。可以用 queue 把保存图片的任务从推理线程里剥离出来,交给一个专门的“后台搬运工”去处理。这样推理线程只管算,算完把图片扔进篮子就走,完全不会堵车。

这是一种典型生产者-消费者模式
推理线程(生产者):只负责计算,算出结果后,把 (图片数据, 文件名) 扔进一个队列,然后立刻返回继续干活。
保存线程(消费者):专门有一个后台线程盯着这个队列,谁把图片扔进来,它就按顺序一张张保存。
设计如下:

self.save_queue=queue.Queue(maxsize=20)# --- 新增:启动后台保存线程 ---self.save_thread=threading.Thread(target=self._save_worker,daemon=True)self.save_thread.start()def_save_worker(self):"""后台线程:专门负责从队列取图片并保存到硬盘"""whileTrue:try:# 从队列获取任务 (阻塞式,没任务时会等待)img_data,img_path=self.save_queue.get()ifimg_dataisNone:break# 收到 None 信号表示退出# 确保目录存在os.makedirs(os.path.dirname(img_path),exist_ok=True)# 执行保存cv2.imwrite(img_path,img_data)print(f"[SaveWorker] 保存成功:{img_path}")# 告诉队列任务完成self.save_queue.task_done()exceptExceptionase:print(f"[SaveWorker] 保存出错:{e}")

效果如下,尽管还是会多次写入,但此时图像每次只保存一张,因此便避免了图像重复写入问题。

显存溢出问题

然而,解决了线程踩踏问题后,又产生了一个新的问题,我的模型在运行时显存不会释放,这就导致发生了显存溢出问题,报错如下:

2026-04-24 15:53:24.2588959 [E:onnxruntime:,sequential_executor.cc:494 onnxruntime::ExecuteKernel] Non-zero status code returned while running Conv node. Name:‘/model.2/cv2/conv/Conv’ Status Message: D:\a_work\1\s\onnxruntime\core\framework\bfc_arena.cc:368 onnxruntime::BFCArena::AllocateRawInternal Available memory of 45793024 is smaller than requested bytes of 134217728

这是模型在执行 /model.2/cv2/conv/Conv时崩溃了。原因是底层的显存分配器(BFCArena)发现,当前显卡上剩余可用显存只有约 43MB(45793024 字节),但当前操作需要申请 128MB(134217728 字节)的连续空间,空间不足导致申请失败。

为什么会这样?
GPU上做卷积运算时,ONNX Runtime会调用NVIDIAcuDNN库。为了追求极致的速度,cuDNN默认会开启一个叫EXHAUSTIVE(穷举)的搜索模式。
它会疯狂尝试几十种不同的卷积算法,试图找出当前显卡上最快的那一个。
为了进行这场“算法大比武”,它需要申请一块巨大的临时工作空间。报错里需要的128MB,正是cuDNN用来做算法测试的临时场地!
在多线程共享显存池的环境下,显存被反复申请、释放,产生了大量的“显存碎片”。虽然总空闲显存可能有几百兆,但没有一块是连续的128MB,导致cuDNN无法搭建测试场地,直接报错崩溃。

显存溢出解决方法

针对这个问题,博主采用了一个比较简单粗暴的方式,限制每个模型分配的显存数量,并且使用使用默认的、最稳妥的卷积算法。
设置如下:

cuda_options={'device_id':0,'gpu_mem_limit':int(1*1024*1024*1024),# 2GB 上限'arena_extend_strategy':'kSameAsRequested',# 严格按需分配'cudnn_conv_algo_search':'DEFAULT'# 【关键新增】禁用穷举搜索,使用默认算法}providers=[('CUDAExecutionProvider',cuda_options),'CPUExecutionProvider']
http://www.jsqmd.com/news/696763/

相关文章:

  • AI Agent的“幻觉“问题:从根源到缓解的完整分析
  • 2026年苏州及周边叉车上岗证培训top5机构盘点:姑苏区n1证/姑苏区叉车上岗证/姑苏区叉车证/学叉车/选择指南 - 优质品牌商家
  • QMCDecode终极指南:如何快速解密QQ音乐加密文件实现跨平台播放
  • ARM SME2指令集:矩阵运算加速与AI性能优化
  • 移动应用开发中的跨平台框架选择与性能对比
  • 安全与权限管理:保障模型与数据资产的安全
  • 从理论到实践:基于扩展卡尔曼滤波(EKF)的永磁同步电机无位置传感器FOC控制
  • 别再傻傻用加法器了!Verilog里这个‘分治’数1技巧,帮你省下FPGA的宝贵资源
  • AI Agent Harness Engineering 的元认知:让它学会评估自身能力与知识边界
  • RWKV-7 (1.5B World) 显存优化部署教程:BF16+单卡强制绑定技巧
  • Web3时代的AI量化是什么?Alpha AI 告诉你答案
  • 手把手教你用Debian Live OS救活CentOS 8:GLIBC升级翻车后的机房急救实录
  • Torch MMCV 深度学习模型报错原因及解决方法汇总(长期更新)
  • 实战部署:在云服务器上快速搭建与运行主流大模型
  • WeDLM-7B-Base算力优化案例:单卡24GB实现32K上下文稳定推理的配置
  • Java转Agent,我替你踩所有坑
  • 企业微信智能机器人一键对接OpenClaw教程
  • WrenAI:基于语义层的自然语言数据查询引擎设计与实践
  • 研发leader如何增强自身在外部就业市场的竞争力
  • NiCE5340 SoM模块:高集成度嵌入式系统开发解析
  • GVHMR
  • 如何快速实现手机号码地理位置定位:ASP.NET解决方案实战指南
  • YOLOv11改进系列 | 原创C3k2_ConvFormerCGLU模块,SepConv Token混合叠加卷积门控FFN,特征表达更强
  • 阿里二面:RAG 检索优化策略有哪些?
  • 告别卡顿!用这个Vue3+TS移动端Table组件,轻松渲染1000条数据
  • Phi-3.5-Mini-Instruct 工业视觉应用:与传统OpenCV算法结合实践
  • linux: 银河麒麟v10安装mysql8
  • NotaGen快速部署:一键启动WebUI,5分钟开始音乐创作之旅
  • 【SQL】SQL同环比计算的多种实现方式
  • Ostrakon-VL 扫描终端 C 语言接口封装实践:为传统应用注入视觉 AI 能力