How to Convert Multiple PDFs with olmOCR: Batch Processing Guide

Use the WorkQueue class with LocalBackend or S3Backend to distribute per-page OCR jobs across async workers, calling process_page from olmocr.pipeline for each page.

olmOCR (allenai/olmocr) is an open-source OCR pipeline that converts PDF pages to markdown using vision language models. To convert multiple PDFs with olmOCR efficiently, you leverage its async WorkQueue infrastructure that treats every PDF page as an independent work unit, enabling parallel processing across local files or cloud storage.

Understanding the olmOCR Batch Architecture

olmOCR employs a pipeline-driven architecture where PDF conversion is split into discrete, asynchronous operations. The system relies on three core components working together:

  • olmocr.pipeline – Implements the end-to-end OCR flow in olmocr/pipeline.py. The public entry point is process_page, which renders a PDF page to an image, builds a VLLM request, and parses the response into a PageResult object.
  • olmocr.work_queue.WorkQueue – Manages a queue of jobs and distributes them to a pool of workers. It supports LocalBackend for filesystem operations or S3Backend for cloud storage.
  • olmocr/bench/runners/run_olmocr_pipeline.py – Demonstrates the minimal async wrapper pattern (lines 42-84) for single-page execution, which scales to batch operations when integrated with the work queue.

The Data Flow for Bulk Conversion

  1. Discover PDFs – Collect absolute paths using standard file walkers or dataset utilities like prepare_olmocrmix.py.
  2. Create a WorkQueue – Instantiate with LocalBackend (local disk) or S3Backend (S3 storage) and configure worker count.
  3. Enqueue jobs – Add PDF paths; the queue automatically normalizes each into per-page tasks.
  4. Process pages – Workers invoke process_page(args, worker_id, pdf_orig_path, pdf_local_path, page_num) for each page.
  5. Collect results – Retrieve PageResult objects containing extracted markdown text.
  6. Shutdown – Workers exit cleanly when the queue empties; the VLLM server remains available for reuse.

Converting Local PDFs in Batch

For processing directories of PDFs on your local machine, combine the WorkQueue with LocalBackend and ensure the VLLM server is running before enqueuing jobs.

Step 1: Configure Pipeline Arguments

Create a configuration object matching the expected arguments for process_page:

import asyncio
import os
from pathlib import Path
from olmocr.pipeline import MetricsKeeper, WorkerTracker, process_page, vllm_server_ready, vllm_server_host
from olmocr.work_queue import WorkQueue, LocalBackend

class Args:
    model = "allenai/olmOCR-2-7B-1025-FP8"
    server = "http://localhost:30044/v1"
    port = 30044
    model_chat_template = "qwen2-vl"
    max_model_len = 16384
    guided_decoding = False
    gpu_memory_utilization = 0.8
    target_longest_image_dim = 1288
    target_anchor_text_len = -1
    max_page_retries = 8
    max_page_error_rate = 0.004
    tensor_parallel_size = 1
    data_parallel_size = 1

args = Args()

Step 2: Ensure VLLM Server Availability

Before processing, verify or start the VLLM server using patterns from run_olmocr_pipeline.py:

async def ensure_server():
    """Start a VLLM server if one is not already running."""
    try:
        await asyncio.wait_for(vllm_server_ready(args), timeout=5)
    except Exception:
        sem = asyncio.Semaphore(1)
        asyncio.create_task(vllm_server_host(args.model, args, sem))
        await vllm_server_ready(args)

Step 3: Process Files with WorkQueue

Initialize the queue, enqueue all PDFs, and run the conversion:

async def convert_pdf_directory(pdf_dir: str):
    await ensure_server()
    
    # Initialize backend and queue with CPU count workers

    backend = LocalBackend()
    queue = WorkQueue(backend=backend, max_workers=os.cpu_count())
    
    # Enqueue every PDF found in directory

    pdf_paths = list(Path(pdf_dir).rglob("*.pdf"))
    for pdf_path in pdf_paths:
        await queue.enqueue_job(str(pdf_path))
    
    # Execute queue - workers call process_page internally for each page

    await queue.run()
    print(f"✅ Converted {len(pdf_paths)} PDFs")

if __name__ == "__main__":
    asyncio.run(convert_pdf_directory("/path/to/pdfs"))

Under the hood, WorkQueue reads each PDF to determine page count, creates a task per page, and distributes them across workers respecting concurrency limits.

Processing PDFs from S3

When source files reside in cloud storage, swap LocalBackend for S3Backend without changing the processing logic:

from olmocr.work_queue import WorkQueue, S3Backend

# Configure S3 backend with your boto3 client

backend = S3Backend(
    s3_client=boto3.client('s3'),
    bucket="my-pdf-bucket",
    prefix="documents/",
    output_bucket="my-output-bucket"  # Optional: for writing results back to S3

)

queue = WorkQueue(backend=backend, max_workers=4)
await queue.run()

The S3Backend automatically downloads PDFs to temporary local storage for processing and can upload markdown results back to the specified output bucket.

Managing Concurrency and Resource Limits

Concurrency is controlled by two key semaphores defined in olmocr/pipeline.py (lines 87-90):

  • max_concurrent_requests_limit – Defaults to 1, limiting simultaneous VLLM API calls.
  • pdf_render_max_workers_limit – Derived from CPU count, limiting parallel PDF rendering operations.

The WorkQueue respects these limits automatically, ensuring you do not overwhelm the VLLM server or exhaust system memory when converting hundreds of PDFs simultaneously.

Summary

  • Use WorkQueue with LocalBackend or S3Backend to orchestrate batch conversions without managing threads manually.
  • Call process_page for each page; it handles rendering, VLLM communication, and retry logic defined in olmocr/pipeline.py.
  • Manage the VLLM server lifecycle using vllm_server_ready and vllm_server_host to ensure the model is available before queuing jobs.
  • Leverage async architecture to process dozens or hundreds of PDFs concurrently, limited only by GPU memory and the max_concurrent_requests_limit semaphore.

Frequently Asked Questions

How does olmOCR handle concurrency when converting multiple PDFs?

olmOCR uses an asyncio-based work queue that treats each PDF page as an independent task. The WorkQueue class spins up multiple worker coroutines (configurable via max_workers) that consume tasks concurrently. Actual concurrency is bounded by semaphores in olmocr/pipeline.py that limit simultaneous VLLM requests and PDF rendering operations to prevent resource exhaustion.

Can I process PDFs stored in S3 with olmOCR?

Yes. Replace LocalBackend with S3Backend from olmocr.work_queue, passing a boto3 client and bucket configuration. The backend handles downloading PDFs to temporary local storage before processing and supports writing markdown outputs directly back to S3 via the output_bucket parameter.

Follow the pattern in olmocr/bench/runners/run_olmocr_pipeline.py: use vllm_server_ready() to check for an existing server, and if none responds within your timeout, launch vllm_server_host() as a background task. This ensures the model loads once and remains available for all subsequent PDFs in your batch, avoiding the overhead of repeated model initialization.

How do I retrieve the extracted markdown text from the pipeline results?

The process_page function returns a PageResult object. Access the extracted text via result.response.natural_text after verifying that result exists and result.is_fallback is False (indicating successful OCR rather than error fallback). When using WorkQueue, attach a callback to the queue or modify the result handler to write outputs to your preferred storage format.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →