首次启动需下载约 6.8 GB 权重,离线环境要先用 huggingface-cli 下载并设置 DSOCR_MODEL_PATH;模型加载完成前 /health 一直返回 503,别当成服务挂了;服务默认无登录且只监听 127.0.0.1,跨机访问需 SSH 隧道或带认证的反向代理。
DeepSeek-OCR-WebUI
neosun100MIT活跃维护自托管的 DeepSeek-OCR-2 网页界面与 API,支持在 NVIDIA、Apple Silicon 和 CPU 上通过 vLLM 或 transformers 运行,提供逐页质量检查、PDF 流式处理和 MCP 接入。
Self-hosted DeepSeek-OCR-2 WebUI & API — vLLM / transformers on NVIDIA, Apple Silicon & CPU, per-page quality checks, PDF streaming, MCP
git clone https://github.com/neosun100/DeepSeek-OCR-WebUI.git && cd DeepSeek-OCR-WebUI
docker compose up -dNVIDIA GPU 可用 vLLM 或 transformers(T4/V100 走 fp16),Apple Silicon 走 MPS,也支持纯 CPU 推理;README 未给出具体显存要求。
自托管 DeepSeek-OCR-2 的 WebUI 与 API,把文档和图片转成带版面框的 Markdown
自托管的 DeepSeek-OCR-2 网页界面与 API,支持在 NVIDIA、Apple Silicon 和 CPU 上通过 vLLM 或 transformers 运行,提供逐页质量检查、PDF 流式处理和 MCP 接入。
核心能力
- vLLM 连续批处理或 transformers 两种推理引擎可选
- 逐页返回 ok 与 warnings 标记重复、截断、坐标异常
- PDF 在 Web Worker 中解析一次并流式逐页返回结果
- 支持 document、ocr、free、figure、describe、find 等多种识别模式
- 提供 MCP 服务,可接入 Claude、Cursor 等客户端
容易踩的坑
- v5.0.0v5.0.0 · DeepSeek-OCR-2 on every GPU you own — vLLM or transformers on CUDA, native Apple Silicon, and CPU, with per-page quality checks you can trust.最新正式版2026-11-07 · 2026-11-07 02:51 UTC
DeepSeek-OCR-2 on every GPU you own — vLLM or transformers on CUDA, native Apple Silicon, and CPU, with per-page quality checks you can trust.
Added
- New backend package
server/deepseek_ocr_webui(managed byuv,pyproject.tomlis the single dependency source) with two engines:- transformers engine that runs on CUDA, Apple Silicon (MPS) and CPU. Measured with
scripts/bench.py: M5 Max (MPS, bf16) 8.1 pages/min; T4 (fp16, eager) 7.8; A10G 9.5 (final verification run). - vLLM engine (continuous batching) that manages a local
vllm serveor load-balances across several external ones (DSOCR_VLLM_URLS, least in-flight). Measured on an A10G: 46.4 pages/min at concurrency 1 and 132.1 at concurrency 16 (transformers: 9.5); both engines agree on the same pages with mean character recall 0.985. Ships with the workarounds vLLM 0.33 needs for OCR-2 (legacy V1 runner for the NGram logits processor,TRITON_ALLOW_NON_CONSTEXPR_GLOBALS=1). See docs/BENCHMARKS.md.
- transformers engine that runs on CUDA, Apple Silicon (MPS) and CPU. Measured with
- Per-page output checks in every response (
ok,warnings[],finish_reason): tail repetition, repetition cut, truncation, coordinate loops, invalid boxes, empty and near-empty output. Repeated tails are stopped while generating (HFStoppingCriteria; vLLM stream closed on detection) and trimmed fromraw_text. - Readiness you can trust:
/healthanswers 503 until the model is loaded and a synthetic warm-up page is read back with ≥ 0.85 character recall. POST /api/ocr/pdf— page-by-page NDJSON stream with heartbeats, per-page errors, andpages=1-3,5ranges.GET /api/capabilities— modes, limits, engine, and known weak spots (with public benchmark evidence).- Swagger is the documentation: every endpoint has a summary and description; docs/openapi.json is generated and checked in CI.
- MCP server on mcp 2.x (
deepseek-ocr-webui-mcp):ocr_image,ocr_pdf,health. - Docker: one Dockerfile with two targets —
vllm(default) andhf; weights are downloaded to a volume on first start instead of being baked in. - GPU verification scripts (
scripts/gpu-verify/), a throughput/quality benchmark (scripts/bench.py), generated README screenshots and badges. - Frontend: model-loading / model-error health states, page-level output warnings, finish reason in the raw-result panel, engine/device/dtype in the status tooltip (en / zh-CN / zh-TW / ja-JP).
Changed
- find mode is now an honest block-level search: DeepSeek-OCR-2 has no native "Locate" prompt (12/12 trials fell back to document parsing), so the server parses the layout and returns the blocks that contain your term.
find_termis now required. - PDF pipeline in the browser: each PDF is parsed once in a worker and its bytes are transferred once; pages are stored as JPEG at 2304 px on the long edge (aligned with the model's 3×768 tiling). A 200-page PDF used 661.9 MB of IndexedDB before and 77.7 MB now; main-thread copies went from 491.7 MB to 0.
- Upload limit for PDFs raised from 100 MB to 500 MB (one constant, enforced by a test) (#51).
- Model weights pinned to revision
aaa02f3(#23); Linuxtorchcomes from the CUDA 12.6 index (works with drivers ≥ 525 via CUDA minor-version compatibility instead of requiring ≥ 580) (#46). - The service binds to
127.0.0.1by default; CORS is off unless you allow-list origins;X-Forwarded-Foris only trusted withDSOCR_TRUST_PROXY=true; compose publishes on127.0.0.1only. - Errors share one shape:
{"detail": {"error", "message", "actual", "limit"}, "request_id"}; the frontend shows the backend message instead of "500 Internal Server Error" (#39). - Logging with loguru and a request id on every line and response (
X-Request-ID). - Development no longer sends documents to a third-party server: the dev API defaults to the same origin with a Vite proxy.
Fixed
- Apple Silicon and CPU inference: upstream
infer()hard-codes.cuda()and bf16, so
- New backend package
用户点评
登录用户的真实使用记录,可按版本和硬件筛选还没有人点评。第一个说出真实体验的人最有价值。
DeepSeek-OCR-WebUI
Self-hosted DeepSeek-OCR-2 for documents and images — vLLM or transformers on NVIDIA, native Apple Silicon, and CPU, with per-page quality checks.
What's new · Screenshots · Quick start · API · Architecture · Performance · Development
✨ What's new in v5.0
v5 is a ground-up renovation of the backend and a hardening of the Vue 3 frontend. Every number below was measured; see CHANGELOG for the full list.
| v4.1 | v5.0 | |
|---|---|---|
| Apple Silicon / CPU | loads, then fails on first page (upstream code hard-codes .cuda()) | works — device-agnostic inference, M5 Max 8.1 pages/min |
| NVIDIA engines | transformers only, one page at a time | vLLM (continuous batching, 132 pages/min on an A10G) or transformers |
| Old GPUs (T4 / V100, no bf16) | BFloat16 vs float crash (#30 #40) | fp16 + eager, verified on T4 |
| Driver requirement | CUDA 13 image ⇒ driver ≥ 580 (#46) | CUDA 12 builds |
| Docker image | 13 GB with weights inside (#38) | weights in a volume; hf image ≈ 4.0 GB unpacked |
| Output quality | HTTP 200 = "success" | per-page ok + warnings (repetition, truncation, coordinate loops, empty) |
| Readiness | /health 200 before the model can read | 503 until a warm-up page is read back correctly |
| Large PDFs | 200 pages ⇒ 662 MB in IndexedDB, freezes (#31 #50) | 78 MB, parsed once in a worker; 500 MB upload limit (#51) |
| Markdown export | dropped multi-box blocks (#49) | 100 % character recall on all samples |
| Security | binds 0.0.0.0, CORS * with credentials, XSS via v-html | 127.0.0.1 by default, CORS allow-list, DOMPurify |
| Tests | 0 backend tests; 123 frontend tests failing on Node 26 | backend, frontend, e2e and mutation suites — live counts in the badges above |
📸 Screenshots
All screenshots are generated by frontend/scripts/screenshots.mjs against a real backend (DeepSeek-OCR-2 on an Apple M5 Max). The demo document is generated by scripts/make_demo_docs.py.
Document → Markdown with layout boxes and a rendered table
| Batch queue | Second PDF page |
|---|---|
| Recognition modes | Service status (engine · device · precision) |
| Swagger UI | Mobile (简体中文) |
🚀 Quick start
🔒 The service has no login. Every command below listens on
127.0.0.1only. To use it from another machine, use an SSH tunnel (ssh -L 8001:127.0.0.1:8001 you@gpu-host) or put it behind a reverse proxy that authenticates users — do not publish the port on all interfaces.
Option 1 — Docker, NVIDIA GPU (vLLM engine, recommended)
git clone https://github.com/neosun100/DeepSeek-OCR-WebUI.git && cd DeepSeek-OCR-WebUI
docker compose up -d # builds the image; first start downloads ~6.8 GB of weights
open http://127.0.0.1:8001 # UI · API docs: http://127.0.0.1:8001/docs
Prebuilt images (published by CI on each release):
docker run -d --gpus all --shm-size 8g -p 127.0.0.1:8001:8001 -v dsocr-models:/models \
ghcr.io/neosun100/deepseek-ocr-webui:5.0.0 # vLLM engine
docker run -d --gpus all --shm-size 8g -p 127.0.0.1:8001:8001 -v dsocr-models:/models \
ghcr.io/neosun100/deepseek-ocr-webui:5.0.0-hf # transformers engine (smaller, also for T4/V100)
/health returns 503 while the model is loading (first start: download + load + warm-up) and 200 once a warm-up page has been read back correctly. docker ps shows healthy at that point.
Option 2 — macOS (Apple Silicon) or Linux without Docker
Requires uv and Node.js ≥ 20.
git clone https://github.com/neosun100/DeepSeek-OCR-WebUI.git && cd DeepSeek-OCR-WebUI
(cd frontend && npm ci && npm run build) # the backend serves the built UI
uv sync --extra hf # transformers + torch (MPS on macOS, CUDA 12.6 wheels on Linux)
uv run deepseek-ocr-webui serve # http://127.0.0.1:8001
On Linux with vLLM instead: uv sync --extra vllm && uv run deepseek-ocr-webui serve --engine vllm.
Offline / air-gapped
Download once (huggingface-cli download deepseek-ai/DeepSeek-OCR-2 --revision aaa02f3811945a91062062994c5c4a3f4c0af2b0 --local-dir /models/ocr2), then start with DSOCR_MODEL_PATH=/models/ocr2.
Configuration
All settings are environment variables with the DSOCR_ prefix (config.py is the single source). The most useful ones:
| Variable | Default | Meaning |
|---|---|---|
DSOCR_HOST / DSOCR_PORT | 127.0.0.1 / 8001 | Listen address. The Docker image sets 0.0.0.0 inside the container; the published port is the boundary. |
DSOCR_ENGINE | auto | auto · vllm · hf (auto = vLLM if installed and CUDA is available) |
DSOCR_DEVICE | auto | transformers engine: cuda · mps · cpu |
DSOCR_REQUIRE_GPU | false | fail fast instead of silently falling back to CPU |
DSOCR_VLLM_URLS | — | comma-separated external vLLM servers (multi-GPU / multi-host, least in-flight) |
DSOCR_MODEL_PATH | — | local weights directory (no network) |
DSOCR_MAX_UPLOAD_MB · DSOCR_MAX_PDF_PAGES | 200 · 500 | upload limits |
DSOCR_MAX_QUEUE · DSOCR_MAX_PER_CLIENT · DSOCR_MAX_PER_IP | 32 · 1 · 4 | admission control (HTTP 429 when exceeded) |
DSOCR_CORS_ORIGINS | — | allow-listed origins (off by default) |
DSOCR_TRUST_PROXY | false | trust X-Forwarded-For (only behind your own proxy) |
v4 variable names (PORT, LOCAL_MODEL_PATH, FORCE_BACKEND) still work.
🧩 Recognition modes
| Mode | Prompt | Output |
|---|---|---|
document (default) | `< | grounding |
ocr | `< | grounding |
free | Free OCR. | plain text, fastest |
figure | Parse the figure. | chart / figure description |
describe | Describe this image in detail. | natural-language description |
find | document parsing + server-side block search | the layout blocks that contain find_term (block-level, see below) |
freeform | your prompt | anything |
About
find: DeepSeek-OCR-2 has no native "locate this phrase" prompt — 12 out of 12 trials with the v1-styleLocate <|ref|>…<|/ref|>prompt fell back to document parsing. v5 therefore parses the layout and returns the boxes of the blocks whose text contains your term. Precision is per block, not per word.
🔌 API
Interactive docs: /docs (Swagger) and /redoc. The OpenAPI spec is checked in at docs/openapi.json and kept in sync by a test.
# One image
curl -s -F [email protected] -F prompt_type=document http://127.0.0.1:8001/api/ocr | jq '{ok, warnings, text}'
# A PDF, streamed page by page (NDJSON: start → page* → done)
curl -sN -F [email protected] -F pages=1-3 http://127.0.0.1:8001/api/ocr/pdf
# Status / capabilities
curl -s http://127.0.0.1:8001/health | jq '{status, ready, engine, warmup}'
curl -s http://127.0.0.1:8001/api/capabilities | jq '.limits, .known_weak'
Each page result contains text, raw_text (with <|ref|>/<|det|> grounding tags), boxes (pixel coordinates), finish_reason, ok and warnings:
| Warning | Meaning | Fails ok? |
|---|---|---|
repetition / repetition_cut | the model looped at the end; the loop is trimmed (and stopped while generating) | yes |
truncated | hit the generation limit | yes |
coordinate_loop | box coordinates degenerated ([23,268,268,268…]) | yes |
empty_output | nothing was generated | yes |
near_empty | ≤ 20 letters/digits — usually a blank page | no |
bbox_invalid | some boxes were out of range and dropped; text kept | no |
find_no_match | find mode: no block contains the term | no |
Errors always look like {"success": false, "detail": {"error", "message", "actual", "limit"}, "request_id"}. The v4 endpoints /ocr, /pdf-to-images and the v4 /health fields keep working.
MCP (Claude, Cursor, …)
{ "mcpServers": { "deepseek-ocr": {
"command": "uv", "args": ["run", "--directory", "/path/to/DeepSeek-OCR-WebUI", "--extra", "mcp", "deepseek-ocr-webui-mcp"],
"env": { "DSOCR_BASE": "http://127.0.0.1:8001" } } } }
Tools: ocr_image(path | image_base64, mode, find_term, custom_prompt, grounding), ocr_pdf(path, mode, pages), health().
🏗 Architecture
- Browser — PDFs are rendered in a Web Worker (each file parsed once, bytes transferred once), pages are kept in IndexedDB so long jobs survive reloads, and Markdown / DOCX / PDF are generated locally.
- Service (
server/deepseek_ocr_webui) — admission control, upload sniffing and pixel limits, the engine, per-page judges, grounding parsing. - Engines —
vllmstreams fromvllm serveand closes the stream as soon as a repetition loop is detected;hfre-implements the upstream preprocessing so tensors follow the model's real device and dtype.
📊 Performance
Measured with scripts/bench.py on dedicated machines (12 pages: sample images, rendered PDF pages, synthetic pages with known text; document mode). Full record, memory, agreement and reproduction steps: docs/BENCHMARKS.md.
| Hardware | Engine | Concurrency | Pages / min | p50 latency | Known-text recall |
|---|---|---|---|---|---|
| NVIDIA A10G 24 GB | vLLM 0.33 | 1 | 46.4 | 0.81 s | 1.00 |
| NVIDIA A10G 24 GB | vLLM 0.33 | 16 | 132.1 | 5.88 s | 1.00 |
| NVIDIA A10G 24 GB | transformers (bf16) | 1 | 9.5 | 2.05 s | 1.00 |
| NVIDIA T4 16 GB | transformers (fp16) | 1 | 7.8 | 2.6 s | 1.00 |
| Apple M5 Max | transformers (MPS, bf16) | 1 | 8.1 | 3.3 s | 1.00 |
vLLM and transformers agree on the same pages with a mean bidirectional character recall of 0.985 (min 0.849 on one dense page). transformers handles one page at a time, so extra concurrency only queues.
🛡 Limits and known weak spots
From GET /api/capabilities (known_weak lists the public benchmark evidence): DeepSeek-OCR-2 is weaker on photos with perspective / skew, handwriting, and low-resource languages. Defaults: 200 MB per upload, 80 MP per image, 500 PDF pages per request (rendered at 144 DPI), 8192 tokens per page.
🧪 Development
uv sync --extra hf --extra mcp --group dev
uv run pytest -n auto tests/unit tests/regression # FakeEngine, no GPU needed
DSOCR_REAL_MODEL=1 uv run pytest tests/e2e/test_real_model.py # real weights on CUDA / MPS
DSOCR_BASE=http://127.0.0.1:8001 uv run pytest tests/e2e # against a running server
cd frontend && npm ci && npx vitest run && npx playwright test --project=chromium
- Mutation testing —
tests/mutations.inilists deliberate bugs (judges, admission, security defaults, readiness, engines, MCP); each must turn the suite red (run with touchstonemutate.py, command in the file header). - GPU verification —
scripts/gpu-verify/builds both images on a GPU machine, runs the e2e suite against each, benchmarks them and compares their outputs. - Generated assets —
node frontend/scripts/screenshots.mjs,uv run python scripts/badges.py [--check],deepseek-ocr-webui openapi > docs/openapi.json. - CI: GitHub Actions (backend · frontend · e2e · image build) and GitLab CI.
Upgrading from v4
- Same port (8001), same UI,
/ocrand/healthstay compatible. The Docker image no longer contains the weights: mount a volume at/models. - Container listens on
0.0.0.0inside, compose publishes on127.0.0.1only — change the publish deliberately if you need LAN access, and add authentication in front of it. findnow requiresfind_termand returns block-level matches.
👥 Contributors
The Vue 3 frontend — including its E2E suite — was contributed by @cloudman6 and is the foundation v5 builds on.
📈 Star history
🤝 Contributing
Issues and pull requests are welcome. Please run the backend and frontend test suites before opening a PR; CI runs them on every pull request.
📄 License
MIT. DeepSeek-OCR-2 weights are distributed by DeepSeek under their own license.
🙏 Acknowledgments
DeepSeek-AI for DeepSeek-OCR-2 · vLLM for the native OCR-2 support and NGram logits processor · @cloudman6 and everyone who reported issues.