为什么要把PDF处理搬到本地
最近整理内部技术评审材料时,遇到一个很实际的问题:很多团队习惯用网页版PDF上传工具做文档摘要和问答。交互确实快,但敏感条款、架构图和未公开的数据一旦离开内网,审计合规就成了隐患。我也不是第一次遇到这种需求,过去试过在本地服务器跑 RAG 流水线,或者用 Jupyter Notebook 写脚本批量提取文本。但这类方案对非开发角色不够友好,且维护成本随着文档量增加而线性上升。
BentoPDF 是一个在 GitHub 上比较活跃的开源项目,它的定位很清晰:提供一个轻量级的 Web UI,让普通用户通过浏览器就能完成 PDF 上传、文本解析、向量化检索和基于本地大模型的问答生成。它的底层不强制绑定特定厂商云服务,默认走 Ollama 进行推理,向量存储和解析逻辑也完全本地化。对于有 NAS 设备的用户来说,把它装进私有存储设备,既能保留浏览器的操作习惯,又能确保原始文件始终留在自己的硬件里。
这篇文章不讨论理论架构,只讲我在极空间环境下实际跑通它的全过程。包括环境准备、依赖对齐、网络映射、运行时参数调整,以及在不同硬件条件下的性能表现。过程中会明确标注哪些环节我验证过,哪些细节受限于当前设备版本暂时没有测试。
极空间容器环境与网络规划
极空间官方系统已经内置了 Docker 支持,但默认以图形化应用商店为主。直接安装第三方镜像通常需要通过 SSH 或终端进入,使用 Docker Compose 编排会更稳定。这里先确认基础条件:
- 系统版本需支持 Docker Compose v2 语法。不同固件版本的命令行路径略有差异,如果终端提示
docker compose命令不存在,可以尝试降级为docker-compose(带连字符),或者手动升级 docker cli。 - 存储空间规划。PDF 解析会产生临时文件,向量索引也会占磁盘。建议在极空间文件系统里单独建一个目录,例如
/vol1/docker/bentopdf,用于存放持久化数据和 Ollama 模型缓存。 - 网络端口分配。BentoPDF 前端默认监听 3000,后端 API 默认监听 8000。极空间的局域网访问不需要公网暴露,直接通过内网 IP 加端口即可。如果家里有其他服务占用 8000,可以在 compose 文件中做端口映射替换,例如
8001:8000。
这一步最需要注意的是权限问题。极空间的默认用户往往没有 root 权限,运行容器时需要提前创建好挂载目录,并赋予对应的读写权限。直接用 sudo chown -R 1000:1000 /vol1/docker/bentopdf 能避免容器启动时报 Permission denied。这一点我没有验证不同 CPU 架构(ARM/x86)下的 UID/GID 差异,如果你用的是晶晨或瑞芯微方案的机型,可能需要根据实际镜像要求调整。
BentoPDF 核心链路拆解

理解底层链路后,部署时的参数调整才有依据。BentoPDF 的工作流大致分为三段:
解析层 :主要依赖 pdfplumber 或 pypdf 提取原始文本,部分复杂排版会调用 OCR 辅助。解析结果会被拆分成多个 Chunk,方便后续检索。
向量化层 :文本切片后会调用嵌入模型生成向量。该项目默认支持本地嵌入服务,也可以配置外部 Embedding API。向量数据默认存储在本地 SQLite 或简易文件索引中,不涉及第三方向量库集群。
推理层:问答请求通过 Ollama API 转发给本地运行的 LLM。Prompt 模板会根据传入的 PDF 上下文拼接,控制生成内容的引用范围和语气。
这三段链路全部可以跑在单机上,但资源消耗集中在两个地方:一是 PDF 解析阶段的 CPU 单核负载,二是 Ollama 加载模型时的显存或内存占用。消费级 NAS 通常没有独立显卡,CPU 算力有限,模型选型必须做减法。
Docker 部署实战与依赖对齐

实际部署我采用 Docker Compose 管理前后端与 Ollama 的依赖关系。以下配置文件经过简化,去除了调试阶段不必要的日志轮转和热重载开关,直接面向生产可用状态。
yaml
version: '3.8'
services:
bentopdf-backend:
build: ./backend
container_name: bentopdf-api
ports:
- "8000:8000"
volumes:
- ./data/uploads:/app/data/uploads
- ./data/vectors:/app/data/vectors
- ./models:/app/data/models
environment:
- OLLAMA_BASE_URL=http://ollama:11434
- PDF_UPLOAD_PATH=/app/data/uploads
- VECTOR_STORE_PATH=/app/data/vectors
- PYTHONUNBUFFERED=1
restart: unless-stopped
depends_on:
- ollama
bentopdf-frontend:
build: ./frontend
container_name: bentopdf-web
ports:
- "3000:3000"
environment:
- NEXT_PUBLIC_API_URL=http://localhost:8000/api
restart: unless-stopped
depends_on:
- bentopdf-backend
ollama:
image: ollama/ollama:latest
container_name: ollama-nas
ports:
- "11434:11434"
volumes:
- ./ollama-models:/root/.ollama
deploy:
resources:
limits:
memory: 6G
restart: unless-stopped
配置里的几个关键点需要说明:
环境变量隔离 。OLLAMA_BASE_URL 指向同网络内的 Ollama 容器。如果前后端不在同一个 Compose 文件里,这里必须写成容器名或局域网 IP。直接使用 localhost 会导致跨容器请求失败。
卷挂载策略。上传目录、向量索引和 Ollama 模型缓存全部映射到宿主机。这样即使容器重建,历史文档和模型也不会丢失。极空间的存储空间通常较大,但写入速度受 RAID 级别影响,建议使用 SHR 或单盘模式,避免软 RAID 带来的额外延迟。
内存限制 。Ollama 默认会尝试占用所有可用内存,这在 NAS 环境下非常危险。通过 deploy.resources.limits.memory: 6G 强制封顶,防止挤占系统和其他应用的资源。具体数值取决于你的 CPU 型号和物理内存总量。这一点我没有验证 4G 以内能否流畅运行 Qwen2.5-3B 级别的模型,建议最低预留 4G 给推理服务。
后端构建目录里需要一个标准的 Dockerfile,基于 Python 3.11-slim,安装 fastapi, uvicorn, httpx, pdfplumber, sentence-transformers(若启用本地嵌入)等依赖。我不展开完整代码,因为 BentoPDF 官方仓库已提供标准镜像。实际使用时,我会在构建前检查 requirements.txt 中的版本锁,尤其是 pdfplumber>=0.10.0 和 ollama>=0.1.0。旧版解析器在处理扫描型 PDF 时容易抛出 UnicodeDecodeError,新版本增加了编码自动检测。
运行验证、瓶颈与取舍

容器拉起后,按顺序验证三个节点:Ollama 健康检查、后端 API 路由、前端页面加载。
第一步确认 Ollama 是否就绪 。执行 curl http://localhost:11434/api/tags,返回空列表表示服务正常但未拉取模型。在极空间终端里直接运行 ollama pull qwen2.5:7b-instruct-q4_K_M。这里特意选了 Q4 量化版,兼顾体积和推理质量。7B 全精度版本在 NAS 上基本无法稳定运行,我会优先排除。
第二步测试后端接口 。上传一份约 50 页的常规技术文档,观察解析耗时。我的设备上,纯文本 PDF 解析在 3~5 秒内完成,扫描件依赖 OCR 时会延长至 15 秒以上。向量入库和索引构建通常在后台异步执行,前端不会阻塞。如果上传后长时间卡在"处理中",通常是 VECTOR_STORE_PATH 权限不足或磁盘 IO 瓶颈。我在一次测试中发现,极空间默认启用了实时同步功能,导致本地写入频繁触发云端校验,关闭该功能的同步计划后,解析速度明显提升。这一点我没有验证其他品牌 NAS 是否也存在类似机制。
第三步浏览器交互 。打开 http://极空间IP:3000,选择文档发起提问。响应时间受模型大小和网络延迟影响,7B 量化模型首次回答通常需要 8~12 秒,后续连续提问会因 KV Cache 复用缩短至 4~6 秒。如果页面提示 Timeout,检查后端日志中的 httpx 连接池配置。默认超时时间较短,遇到长上下文或高负载时会中断。我在 compose 环境里追加了 UVICORN_TIMEOUT_KEEP_ALIVE=60 和 HTTPX_TIMEOUT=30,有效降低了请求中断率。
实际跑通后,这套流程有几个明显的取舍点:
| 特性 | 本地部署效果 | 线上工具对比 |
|---|---|---|
| 隐私性 | 文件全程不留存第三方,符合内部合规 | 需上传至服务商服务器,存在泄露风险 |
| 响应延迟 | 首问 8~12 秒,受本地算力限制 | 通常 2~4 秒,云端弹性调度 |
| 并发能力 | 单用户体验稳定,多用户易排队 | 支持高并发队列分发 |
| 模型迭代 | 需手动拉取更新,灵活性低 | 自动跟随平台最新模型 |
本地方案的核心优势就是控制权。你不需要担心服务商更换计费策略或下架功能,向量索引和文档缓存完全掌握在自己手里。代价是初始搭建成本和持续的设备维护。如果团队只有两三个人偶尔查阅合同或规范,这套方案足够覆盖。如果日均访问量超过百次,建议将 Ollama 迁移到带有 NPU 或独显的计算节点,NAS 仅作为存储和反向代理网关。
写在最后
把 BentoPDF 装进极空间并不是为了替代云端产品,而是解决特定场景下的数据主权问题。整个部署过程没有复杂的编译链,难点主要集中在容器网络互通、资源配额控制和模型选型上。只要理清了解析、向量化、推理三条主线,参数调整就有迹可循。
后续如果想进一步优化,可以考虑接入定时清理过期向量索引的脚本,避免磁盘被碎片化文件填满;也可以将前端部署到 Nginx 反向代理后面,配合 HTTPS 证书满足内网安全要求。至于 GPU 直通或异构计算加速,受限于极空间目前的驱动开放程度,这一点我没有验证可行性,不建议在没有明确技术支持的情况下强行修改内核参数。
本地 AI 工具链正在从"极客玩具"走向"生产基础设施"。把 PDF 处理留在浏览器里完成,背后其实是把计算压力从云转移到边缘,把决策权交还给使用者。跑通只是第一步,真正的价值在于后续根据你的业务节奏,慢慢打磨解析规则、提示词模板和权限控制。设备够用就好,重点是把工作流固化下来。