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_path3. 正确的显存回收函数(顺序不能乱)
必须先等 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.3比naive_best_fit友好得多
改完同图识别:基线 576 → 峰值 ~680 → 清完回 580 左右,不再翻倍。
