PaddleOCR GPU 推理显存不释放?这样改,显存稳稳回落不翻倍--亲测有效

PaddleOCR GPU 推理显存不释放?这样改,显存稳稳回落不翻倍--亲测有效

PaddleOCR GPU 推理显存不释放?这样改,显存稳稳回落不翻倍

现象

Flask 包一层 PaddleOCR 3.x 做 OCR 服务,启动基线 576MiB,识别同一张图:

  • 旧版(naive_best_fit)涨到 670MiB 左右就稳了

  • 新版(auto_growth)直接飙到 1400+ MiB,nvidia-smi 看着像泄漏

实测不是真泄漏,是分配器策略 + 清理姿势不对 + 预处理画蛇添足三件事叠一起。

根因一句话

Paddle 的 GPU 显存分配器默认会把“释放的块”囤在自己池子里不还给驱动,empty_cache()能不能把显存退给 nvidia-smi,取决于分配策略:

  • naive_best_fit(旧):预占一大块,池子锁死,empty_cache()基本无效

  • auto_growth(新,pip 版默认):按需cudaMalloc,空闲块可以被empty_cache()归还 ✅

但很多人用了auto_growth还是不回落,是因为清理顺序错了,或者预处理把输入 shape 搞出花样,导致 cuDNN workspace 越缓存越多。

解决办法(四步,照抄)

1. import paddle 之前设环境变量

import os # 关键:换 auto_growth,empty_cache 才管用 os.environ["FLAGS_allocator_strategy"] = "auto_growth" os.environ["FLAGS_fraction_of_gpu_memory_to_use"] = "0.3" # 30% 上限兜底 os.environ["FLAGS_initial_gpu_memory_in_mb"] = "512" os.environ["FLAGS_reallocate_gpu_memory_in_mb"] = "128" os.environ["FLAGS_cudnn_exhaustive_search"] = "0" os.environ["FLAGS_conv_workspace_size_limit"] = "64"

必须放在import paddle/import paddleocr最前面,晚了不生效。

2. 预处理别瞎缩放对齐

显存是被 det 的text_det_limit_side_len=960和 rec 固定高度卡死的,外部预缩放省不到显存,只会让小字识别率掉

不要做 32 倍数对齐,不要强行 resize 到小图,只转 RGB:

def preprocess_image(image_path): from PIL import Image with Image.open(image_path) as img: if img.mode not in ('RGB', 'L'): img.convert('RGB').save(image_path) return image_path

3. 正确的显存回收函数(顺序不能乱)

必须先等 kernel 跑完 → Python 断引用 → 再等一次 → 最后empty_cache()

import gc import paddle def clean_gpu_memory(force=False): # 节流:每 N 次清一次,别每次请求都 synchronize if not force: clean_gpu_memory._n += 1 if clean_gpu_memory._n < 15: return clean_gpu_memory._n = 0 paddle.device.synchronize() # 2.5+ 新 API,替代 cuda.synchronize() gc.collect() # 断 Python 侧 tensor 引用 paddle.device.synchronize() paddle.device.cuda.empty_cache() # 只有 auto_growth 下能真退回显存 clean_gpu_memory._n = 0

paddle.device.cuda.synchronize()2.5.0 起弃用,换成paddle.device.synchronize()

4. 推理完在 finally 里清一次

python

def process_single_image(path): start = time.time() result = None try: with _ocr_lock: # predictor 非线程安全,加锁 result = ocr.predict(preprocess_image(path)) return {'texts': parse(result), 'time_cost': round(time.time()-start, 4)} finally: del result clean_gpu_memory()

并发用 Flask 默认多线程时,Paddle inference predictor 不是线程安全的,不加锁显存峰值会翻倍,结果还可能串。

验证

服务起好后看这两个值:

curl http://localhost:19600/gpumem curl "http://localhost:19600/gpumem?clean=1"
  • allocated_mb每次识别完回落到基线 → 正常

  • reserved_mb高但 allocated 回落 → 只是池子缓存,不是泄漏

  • 强制 clean 后 reserved 往下掉 → auto_growth 生效

避坑补充

  • 不要在推理外面套paddle.no_grad():走的是 inference predictor,没有反向图,写了也没用

  • 不要在每次predict后都synchronize():阻塞等 kernel,白等;只在 clean 里同步

  • 文件大小限制在preprocess_image里 raise 别被同函数 except 吞掉,要往外抛

  • 同卡跑别的任务,用auto_growth+fraction=0.3naive_best_fit友好得多

改完同图识别:基线 576 → 峰值 ~680 → 清完回 580 左右,不再翻倍。