Unlimited-OCR Documentation

Unlimited OCR Works: Welcome the Era of One-shot Long-horizon Parsing.

20,857 stars Python Python 1 file ยท ~1,377 tokens
RAW Doc

Unlimited OCR Works

Welcome the Era of One-shot Long-horizon Parsing.

Release

  • [2026/07/21] ๐Ÿค Thanks to the ms-swift community for their support, our model now supports training with ms-swift.
  • [2026/07/03] ๐Ÿค Thanks to the Baidu Cloud team for their support. Our model is now available on Baidu Cloud.
  • [2026/06/28] ๐Ÿค Thanks to the vLLM community and Tianyu Guo for their support, our model now supports vLLM inference.
  • [2026/06/24] ๐Ÿค Thanks to AK for creating a demo for us. It is now available at Hugging Face Spaces.
  • [2026/06/23] ๐Ÿ“„ Our paper is now available on arXiv.
  • [2026/06/23] ๐Ÿค Thanks to the ModelScope community for their support. Our model is now available at ModelScope.
  • [2026/06/22] ๐Ÿš€ We present Unlimited-OCR, aiming to push Deepseek-OCR one step further.

Inference

Transformers

Inference using Huggingface transformers on NVIDIA GPUs. Requirements tested on python 3.12.3 + CUDA12.9๏ผš

text
torch==2.10.0
torchvision==0.25.0
transformers==4.57.1
Pillow==12.1.1
matplotlib==3.10.8
einops==0.8.2
addict==2.4.0
easydict==1.13
pymupdf==1.27.2.2
psutil==7.2.2
text
/* Detailed source-code truncated for AI context efficiency. */

vLLM

Please refer to the official vLLM recipe for deployment details:

Recipe: https://recipes.vllm.ai/baidu/Unlimited-OCR

Docker Images

Use the following Docker images depending on your GPU platform:

Default (CUDA 13.0):

bash
docker pull vllm/vllm-openai:unlimited-ocr

For Hopper GPUs (CUDA 12.9)

bash
docker pull vllm/vllm-openai:unlimited-ocr-cu129

SGLang

Set up the environment (uv-managed virtualenv). Install the local SGLang wheel first,
then pin kernels==0.9.0 and install PyMuPDF for PDF-to-image conversion:

shell
uv venv --python 3.12
source .venv/bin/activate

uv pip install wheel/sglang-0.0.0.dev11416+g92e8bb79e-py3-none-any.whl
uv pip install kernels==0.11.7
uv pip install pymupdf==1.27.2.2

Start the SGLang server:

shell
python -m sglang.launch_server \
    --model baidu/Unlimited-OCR \
    --served-model-name Unlimited-OCR \
    --attention-backend fa3 \
    --page-size 1 \
    --mem-fraction-static 0.8 \
    --context-length 32768 \
    --enable-custom-logit-processor \
    --disable-overlap-schedule \
    --skip-server-warmup \
    --host 0.0.0.0 \
    --port 10000

Send streaming requests to the OpenAI-compatible API:

text
/* Detailed source-code truncated for AI context efficiency. */

For batch inference, infer.py starts the SGLang server automatically and sends concurrent requests for an image directory or PDF:

shell
# Image directory
python infer.py \
    --image_dir ./examples/images \
    --output_dir ./outputs \
    --concurrency 8 \
    --image_mode gundam

# PDF pages
python infer.py \
    --pdf ./examples/document.pdf \
    --output_dir ./outputs \
    --concurrency 8 \
    --image_mode gundam

Useful options:

shell
--model_dir baidu/Unlimited-OCR   # Local path or Hugging Face model ID
--gpu 0                           # CUDA_VISIBLE_DEVICES value
--server_log ./log/sglang_server.log

For OmniDocBench evaluation, you need to perform the following post-processing.

python
DET_RE = re.compile(r'<\|det\|>([^<\s]+)(?:\s*\[[^\]]*\])?\s*<\|/det\|>(.*)', re.DOTALL)

def remove_det(raw: str) -> str:
    """
    Strip <|det|>type [bbox]<|/det|> markers, group lines belonging to the
    same block with \\n, and separate different blocks with \\n\\n.
    """
    blocks = []
    cur = None
    for line in raw.splitlines():
        line = line.rstrip()
        if not line:
            continue
        m = DET_RE.match(line)
        if m:
            category, content = m.group(1).strip(), m.group(2).strip()
            if category == 'image':
                continue
            if cur is not None:
                blocks.append(cur)
            cur = [content] if content else []
            continue
        if cur is None:
            cur = []
        cur.append(line)
    if cur is not None:
        blocks.append(cur)
    text = '\n\n'.join('\n'.join(b) for b in blocks).strip()
    return text

Visualization

Acknowledgement

We would like to thank Deepseek-OCR, Deepseek-OCR-2, PaddleOCR for their valuable models and ideas.

Citation

bibtex
@misc{yin2026unlimitedocrworks,
      title={Unlimited OCR Works}, 
      author={Youyang Yin and Huanhuan Liu and YY and Qunyi Xie and Chaorun Liu and Shiqi Yang and Shaohua Wang and Zhanlong Liu and Hao Zou and Jinyue Chen and Shu Wei and Jingjing Wu and Mingxin Huang and Zhen Wu and Guibin Wang and Tengyu Du and Lei Jia},
      year={2026},
      eprint={2606.23050},
      archivePrefix={arXiv},
      primaryClass={cs.CV},
      url={https://arxiv.org/abs/2606.23050}, 
}