Skip to content

Commit d6fa93e

Browse files
davanstrienclaude
andauthored
ocr: add serving-unlimited-ocr.md (serve Unlimited-OCR as a live endpoint on HF Jobs) (#61)
* ocr: add serving-unlimited-ocr.md — serve Unlimited-OCR as a live endpoint on HF Jobs A worked example of HF Jobs serving: spin up baidu/Unlimited-OCR as a temporary OpenAI-compatible GPU endpoint with one command, call it (single image + multi-page PDF), batch via concurrency, and stop it. Adds a short "Serve a model as a live endpoint" pointer in ocr/README.md. Validated 2026-06-23 on h200 + fa3: stock lmsysorg/sglang image + the model's 12MB custom SGLang wheel (pip install --no-deps) + kernels==0.11.7. Additive only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ocr/serving-unlimited-ocr.md: defer hardware specs/pricing to `hf jobs hardware` (review) Hardcoded h200 pricing and the 141GB memory figure can rot; point readers to `hf jobs hardware` for current flavors + prices instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ocr serving doc: tighten to factual prose Remove flourishes (coffee analogy, rhetorical framing, 'self-destructs') from the serving doc and the README section; state behaviour plainly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 109537a commit d6fa93e

2 files changed

Lines changed: 101 additions & 0 deletions

File tree

ocr/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ This will:
3131
- Push the results to a new dataset
3232
- View results at: `https://huggingface.co/datasets/[your-output-dataset]`
3333

34+
## Serve a model as a live endpoint
35+
36+
The recipes here run as batch jobs. To call a model interactively, from an agent, or with concurrent ad-hoc requests, you can instead run it as a temporary endpoint: [HF Jobs serving](https://huggingface.co/docs/hub/jobs-serving) exposes a port on a GPU Job, giving an OpenAI-compatible endpoint that runs until the job is cancelled or its `--timeout` is reached. See [serving-unlimited-ocr.md](serving-unlimited-ocr.md) for a worked example serving Baidu's [Unlimited-OCR](https://huggingface.co/baidu/Unlimited-OCR) with SGLang.
37+
3438
## Models at a glance
3539

3640
**Start here:** for a quick first run, try **`lighton-ocr2.py`** (1B, very fast) or **`paddleocr-vl-1.6.py`** (0.9B, current OmniDocBench SOTA); for the smallest footprint, **`falcon-ocr.py`** (0.3B, strong on tables). Reach for a 7–8B model only when quality demands it. Several of these models sit on the public [olmOCR-Bench](https://huggingface.co/datasets/allenai/olmOCR-bench) — pull the live ranking from your terminal in one command:

ocr/serving-unlimited-ocr.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Serve Unlimited-OCR as a live endpoint on HF Jobs
2+
3+
The OCR recipes in this folder run as batch jobs (dataset in → dataset out). To call a model
4+
interactively, from an agent, or with ad-hoc concurrent requests, you can instead run it as a
5+
temporary HTTP endpoint. [HF Jobs serving](https://huggingface.co/docs/hub/jobs-serving) exposes a
6+
port on a GPU Job, giving an OpenAI-compatible endpoint that runs until the job is cancelled or its
7+
`--timeout` is reached.
8+
9+
This is a worked example for [baidu/Unlimited-OCR](https://huggingface.co/baidu/Unlimited-OCR)
10+
(3B, MIT, based on DeepSeek-OCR; supports multi-page parsing in a single request). The model ships
11+
its own SGLang build, so it runs on the stock `lmsysorg/sglang` image with the 12 MB wheel
12+
installed at startup; no custom image is required.
13+
14+
## 1. Start the server
15+
16+
```bash
17+
hf jobs run --detach --expose 10000 --flavor h200 -s HF_TOKEN --timeout 30m \
18+
lmsysorg/sglang:latest -- \
19+
bash -lc 'pip install --no-deps https://github.com/baidu/Unlimited-OCR/raw/main/wheel/sglang-0.0.0.dev11416+g92e8bb79e-py3-none-any.whl \
20+
&& pip install -q kernels==0.11.7 \
21+
&& python -m sglang.launch_server --model baidu/Unlimited-OCR --served-model-name Unlimited-OCR \
22+
--attention-backend fa3 --page-size 1 --mem-fraction-static 0.8 --context-length 32768 \
23+
--enable-custom-logit-processor --disable-overlap-schedule --skip-server-warmup \
24+
--host 0.0.0.0 --port 10000'
25+
```
26+
27+
Notes:
28+
- `--` before `bash` is required, or the CLI parses `-lc` as its own flags.
29+
- `--timeout` stops the endpoint (and billing) at the deadline; `hf jobs cancel <id>` stops it earlier.
30+
- `fa3` requires a Hopper GPU (e.g. `h200`). The model is small, so the attention backend, not GPU
31+
memory, determines the flavor. Run `hf jobs hardware` for available flavors.
32+
- Follow startup with `hf jobs logs -f <id>`; the server is ready at `Application startup complete`
33+
(about 3 minutes from a cold start).
34+
35+
## 2. Call it (OpenAI client; HF token as the API key)
36+
37+
The exposed port is at `https://<job_id>--10000.hf.jobs`; the OpenAI base URL is that plus `/v1`.
38+
39+
```python
40+
import base64, os
41+
from openai import OpenAI
42+
43+
client = OpenAI(base_url="https://<job_id>--10000.hf.jobs/v1", api_key=os.environ["HF_TOKEN"])
44+
img = base64.b64encode(open("page.jpg", "rb").read()).decode()
45+
46+
r = client.chat.completions.create(
47+
model="Unlimited-OCR",
48+
messages=[{"role": "user", "content": [
49+
{"type": "text", "text": "document parsing."},
50+
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{img}"}},
51+
]}],
52+
temperature=0,
53+
extra_body={"images_config": {"image_mode": "gundam"}}, # "gundam" (crop-tiling) or "base"
54+
)
55+
print(r.choices[0].message.content)
56+
```
57+
58+
Output is layout-grounded markdown: each block is tagged `<|det|>type [x1,y1,x2,y2]<|/det|> text`,
59+
with coordinates normalized to 0–1000. Remove the tags for plain text
60+
(`re.sub(r'<\|det\|>.*?<\|/det\|>', '', text)`) or keep them for structure.
61+
62+
## 3. Multi-page / PDF
63+
64+
Send multiple page images in one request with the `Multi page parsing.` prompt and `image_mode="base"`:
65+
66+
```python
67+
parts = [{"type": "text", "text": "Multi page parsing."}]
68+
for page_png in page_images: # e.g. PDF pages rendered with pymupdf at ~150 dpi
69+
b64 = base64.b64encode(open(page_png, "rb").read()).decode()
70+
parts.append({"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}})
71+
72+
r = client.chat.completions.create(
73+
model="Unlimited-OCR",
74+
messages=[{"role": "user", "content": parts}],
75+
temperature=0, max_tokens=16384,
76+
extra_body={"images_config": {"image_mode": "base"}},
77+
)
78+
```
79+
80+
Pages are separated by `<PAGE>`; tables are returned as HTML and equations as LaTeX, with reading
81+
order preserved across pages. The context length is 32k tokens, so split longer documents.
82+
83+
## 4. Concurrency
84+
85+
SGLang batches concurrent requests, so a client can send many requests in parallel to one endpoint;
86+
the upstream [`infer.py`](https://github.com/baidu/Unlimited-OCR/blob/main/infer.py) uses a
87+
`ThreadPoolExecutor` at `concurrency=8`. For a large corpus, a batch job that runs next to the data
88+
(resumable, no network transfer) is usually a better fit than a client-to-endpoint loop.
89+
90+
## 5. Stop it
91+
92+
```bash
93+
hf jobs cancel <job_id>
94+
```
95+
96+
Billing is per-minute for the GPU flavor plus a small flat fee for the exposed port; scheduling time
97+
is not billed. Run `hf jobs hardware` for current flavors and prices.

0 commit comments

Comments
 (0)