为什么间歇性OOM:
- 临时对象未清理:每个PDF转换产生大量临时对象(rendered、text、images),这些对象占用GPU内存但没有立即释放
- 清理不同步:
torch.cuda.empty_cache()是异步的,不保证立即生效 - 累积效应:简单PDF显存占用小,复杂PDF占用大,累积后突然OOM
def convert_file(self, pdf_path: str) -> tuple[str, dict]:
# 转换
rendered = self.converter(pdf_path)
text, _, images = text_from_rendered(rendered)
# 立即删除临时对象
del rendered
del text
del images
# 强制垃圾回收
import gc
gc.collect()
# 清空CUDA缓存
torch.cuda.empty_cache()
torch.cuda.synchronize() # 关键:强制同步,确保清理完成
return result_text, result_images关键改进点:
- 每个文件都清理,不再是每3个文件
- 删除临时变量:rendered、text、images立即删除
- 强制同步:
torch.cuda.synchronize()确保清理立即生效,不是异步等待
# 在api.py中
if "CUDA OOM" in error_msg:
print("[Batch] Attempting fallback to CPU...")
r = convert_file(..., device="cpu", use_cache=False)优点:
- GPU失败自动切CPU
- 保持公式识别和图片裁切
- 用户无需干预
# 每5个文件
if i % 5 == 0:
torch.cuda.empty_cache()
torch.cuda.synchronize()
# 每10个文件
if i % 10 == 0:
gc.collect()
clear_converter_cache()marker-pdf模型显存占用:
- Layout模型:~1GB(检测版面、图片、表格、公式区域)
- OCR模型:~1.5GB(识别文字和公式)
- Table模型:~0.5GB(表格结构识别)
临时显存占用(每个文件):
- 简单PDF(10页纯文本):~0.5GB
- 复杂PDF(50页含图片公式):~2GB
- 超大PDF(100+页):~4GB+
nuoyi ./papers --batch --device cuda --existing-files update- 简单PDF用GPU快速处理
- 复杂PDF自动切CPU
- 公式和图片完美保留
- 无需用户干预
nuoyi ./papers --batch --device cuda --low-vram --existing-files update- OCR模型放CPU,Layout模型放GPU
- 显存占用降低到~1.5GB
- 速度略慢(OCR在CPU)
- 公式和图片保留
nuoyi ./papers --batch --device cuda --disable-ocr-models --existing-files update- 禁用OCR模型
- 显存占用~1GB
- 只适合纯数字文本PDF
- 公式识别和图片裁切保留
- 扫描版PDF不能用
# 先用GPU快速处理简单PDF
nuoyi ./papers --batch --device cuda --low-vram --existing-files update
# 失败的再单独处理
nuoyi ./failed --batch --device cpumarker-pdf核心优势(不能放弃):
-
公式识别:
- 自动检测公式区域
- 提取LaTeX代码
- 转换为可渲染的Markdown公式
- pymupdf做不到
-
图片智能裁切:
- 检测图片边界
- 裁剪空白边缘
- 优化分辨率
- pymupdf只能简单提取
-
OCR:
- 扫描版PDF识别
- 手写文字识别
- pymupdf无OCR
结论:必须用marker-pdf,不能换pymupdf。
# 实时监控GPU显存
watch -n 1 nvidia-smi
# 或者
nvidia-smi -l 1监控指标:
- Memory-Usage:已用显存
- FB Memory Usage:帧缓存使用
- GPU-Util:GPU利用率
44315个PDF推荐流程:
- 分批处理:每批5000个文件,避免长时间运行累积问题
- 保存进度:
--existing-files update避免重复处理 - 监控显存:
nvidia-smi -l 1实时监控 - 定期重启:每批处理完重启清理所有内存
# 批处理脚本
for i in {1..9}; do
nuoyi ./batch_$i --batch --device cuda --existing-files update
sleep 10 # 给GPU喘息时间
done| 方案 | 公式识别 | 图片裁切 | 速度 | 显存 | 稳定性 |
|---|---|---|---|---|---|
| GPU自动降级 | ✓ 完美 | ✓ 完美 | 快 | 3-4GB | ★★★★★ |
| 低VRAM模式 | ✓ 完美 | ✓ 完美 | 中 | 1.5GB | ★★★★☆ |
| disable-ocr | ✓ 完美 | ✓ 完美 | 快 | 1GB | ★★★★☆ |
| 纯CPU | ✓ 完美 | ✓ 完美 | 慢10倍 | 0GB | ★★★★★ |
| pymupdf | ✗ 无 | △ 简单 | 极快 | 0GB | ★★★★★ |
改进前:
- 简单PDF + 复杂PDF混合 = 间歇性OOM
- 显存累积,清理不及时
- 需要手动重启或切换设备
改进后:
- 每个文件强制清理临时显存
- GPU同步确保清理立即生效
- OOM自动降级CPU,无需干预
- 稳定性大幅提升
为什么需要synchronize():
torch.cuda.empty_cache() # 异步,提交清理请求
torch.cuda.synchronize() # 同步,等待清理完成empty_cache()只是标记要清理,不保证立即完成synchronize()强制等待GPU完成所有操作- 组合使用才能确保显存真正释放
为什么删除临时变量:
del rendered, text, images- 这些对象可能持有GPU内存引用
- Python引用计数可能延迟释放
- 显式删除+gc.collect()确保立即释放