IronStratum

PDF to markdown for RAG: the parser showdown

The reliable way to turn a PDF corpus into RAG input is to convert to markdown first, then chunk the markdown by its headings, and this guide shows that path with the artifacts intact: a three-page test PDF run through three local parsers with every output quoted verbatim, a comparison table with dated cost per page from official rate cards, a runnable retrieval check that scores each parsing choice against a fixed question set, and a clear line for when running your own parsing stack stops paying.

Every parser output below was captured by running the tool in a container, not written by hand. The corpus generator, the parser runner, and the retrieval scorer ship with this guide, so you can regenerate each exhibit before trusting any claim. Where a tool was not run here, the section says so and sources what it claims instead.

Why markdown comes first

A RAG pipeline is a chain: parse, chunk, embed, retrieve. Each stage consumes the previous one, so a parse defect is inherited by everything downstream. A chunk split in the wrong place embeds as an incoherent unit; a table flattened into a string of numbers loses the mapping that made the numbers answerable; a running header that survives parsing dilutes chunk after chunk.

Markdown is the standard intermediate format because its structure carries what a chunker needs: headings mark where topics begin and end, and table rows keep the relationship between a value and its label. None of that structure exists in a PDF itself. The format stores positioned characters, not sections, so every parser is reconstructing structure, and reconstruction is where tools differ.

The practitioners in this lane say it plainly. On the retrieval-engineering forum where parser shortlists get debated, the 2026 buyer thread asking which tool handles complex PDFs, tables, and scans drew the reply scored highest in the thread: parsing is "the hardest challenge in RAG systems" and "not really a one-size fits all solution". The same thread's second-highest answer is the checklist to hold any parser to: structure preservation, table accuracy, page and citation tracking, failure visibility, cost, and latency, past just text extraction quality. This guide tests against that list, with the artifacts to check.

The corpus, and what it is not

The test document is a three-page technical report built by script, containing the four things that stress a parser on real work: two-column body text, section headings inside the columns, one ruled specification table with numeric values, and a running header and footer on every page plus a footnote on the last. The generator draws it with real layout machinery and ships with this guide as make_corpus.py.

Honesty about the corpus, because it bounds every result below. It is born-digital, with a real text layer, so nothing here exercises OCR. It is synthetic. And its drawing order matches its reading order, so it does not reproduce the out-of-order content streams that make some real PDFs interleave under simple extractors. A three-page clean corpus flatters every parser, and that is deliberate: when even a clean corpus breaks a tool, that tool will not survive your production documents. The scale-up advice from the same forum thread: take 20 to 50 real documents from your workflow and benchmark. This page shows the method on three.

The showdown: three parsers, one PDF

Three local parsers ran on the corpus in a container: PyMuPDF4LLM, Microsoft's MarkItDown, and IBM Research's Docling. Wall clock on this machine, CPU only, first run each: MarkItDown 0.4 seconds, PyMuPDF4LLM 2.2 seconds, Docling 29.1 seconds after a one-time model download measured in hundreds of megabytes. That ordering is the known speed class of the three, and it is the first trade you are making.

MarkItDown: fast, and it shows

MarkItDown describes itself as a lightweight utility for converting many file formats to markdown for LLM use, PDF being one format among Word, slides, spreadsheets, and images (MarkItDown README). On PDF it has no layout model: it reads the text runs and emits them. The captured output starts, verbatim, like this:

2. Mechanical clearance limits

Impeller clearance is measured with feeler gauges
at four points around the wear ring, and the
largest of the four readings is the value of record.

The document's actual first page is a title, an abstract, and section 1. The output opens with section 2, which sits in the right-hand column of page 1; the title block appears sixty lines later. Reading order came out scrambled relative to the visual document, the text carries hard line breaks from the PDF's visual line ends, and no heading arrived as markdown. The table fared worse. Here is the captured table region, exactly as produced:

Class

Point

Service

Replace

A

B

C

D

wear ring

wear ring

wear ring

wear ring

0.25

0.30

0.35

0.40

0.40

0.45

0.50

0.55

Every value survived. The table did not: the cells were serialized column by column, so nothing in the output says that 0.35 is the service limit for class C. For retrieval this is the worst shape of loss, because a search for clearance numbers can still hit the chunk while the mapping needed to answer is gone. The running header and page numbers landed inline in the body text, three times each.

At 0.4 seconds the tool is nearly free to run, and where you only need the words, not the structure, that is a real trade in its favor. Its PDF path is the weak leg, not the tool: practitioners keep it in the stack for Office formats even when Docling handles PDFs.

PyMuPDF4LLM: structure recovered, columns interleaved

PyMuPDF4LLM is the LLM-oriented extension of PyMuPDF, documented with markdown, JSON, and plain-text output, multi-column support, page-chunk output, and header and footer exclusion switches (PyMuPDF4LLM docs). On the corpus it detected the heading hierarchy from font sizes and rendered the table as a real markdown table, captured verbatim:

## **2. Mechanical clearance limits**

Impeller clearance is measured with feeler gauges at four points around the wear ring, and the largest of the four readings is the value of record. The measured value is compared against the class limits in table 1. A value above the replacement limit takes the pump out of service the same day.

**Table 1. Impeller class clearance limits (millimetres)**

|**Class**|**Point**|**Service**|**Replace**|
|---|---|---|---|
|A|wear ring|0.25|0.40|
|B|wear ring|0.30|0.45|
|C|wear ring|0.35|0.50|
|D|wear ring|0.40|0.55|

That table is answerable: each value sits in its row with its label. The catch is reading order. Page 1 of the corpus has two columns, and in the captured output the second and third paragraphs of section 1, which belong to the left column, appear after the section 2 content from the right column. The blocks were ordered by vertical position across the full page, which interleaves columns instead of finishing one before the next. The headings are right, the table is right, and a reader who knows the document can see that two paragraphs migrated. A retriever cannot: those paragraphs embed in the wrong neighborhood. Test your own two-column documents before trusting this tool's ordering.

The furniture switches work as documented: the same call with header=False, footer=False removed exactly the twelve furniture lines the default output carries, three running headers, three page-number lines, and six blank seams. One flag pair, before embedding, instead of a regex layer after it.

Docling: the layout model earns its runtime

Docling is IBM Research's document converter, MIT-licensed, built around layout and table-structure models, with OCR support for scans and an intermediate document object exported to markdown, JSON, or HTML (Docling README). Its captured output is the only one of the three with the reading order fully correct: title, abstract, sections 1 through 4, appendix, footnote. The header and footer never appeared in the body at all; the layout model classified them as furniture and dropped them without switches. The table came out aligned and complete:

Table 1. Impeller class clearance limits (millimetres)

| Class   | Point     |   Service |   Replace |
|---------|-----------|-----------|-----------|
| A       | wear ring |      0.25 |      0.40 |
| C       | wear ring |      0.35 |      0.50 |

The excerpt above drops rows B and D for space; the captured file has all four. Docling's one visible defect on this corpus: the appendix heading was not detected as a heading, so the line "Appendix A. Gauge set inventory at the regional depot" fused into the following paragraph, and one chunk boundary disappeared with it. Models predict structure, and predictions are mostly right. The retrieval section shows what that single lost boundary did to one question.

The parsers not run here

Two local tools in the comparison table were not run for this guide, and the reasons are the honest kind. Marker, Datalab's converter, ships its OCR model behind a local inference server that wants a GPU backend or a CPU serving runtime (Marker README); its row below comes from its official documentation and an engineering lead's three-way measurement dated August 2026 (danilchenko, MarkItDown vs Docling vs Marker). MinerU, from the OpenDataLab project, downloads its own model set on first run for either of its two backends (MinerU README); same rule: documentation and dated secondary testing, never invented output. LlamaParse and Mistral OCR are hosted services; their costs below are their official rate cards and their output claims are their own docs.

The comparison table

Prices below are list rates read from official pages on 11 September 2026. The local rows cost no per-page fee: you pay in hardware, runtime, and maintenance. The dollar column reads US dollars per 1,000 pages.

ToolLicenseRunsOCR pathTablesCost per 1,000 pages
PyMuPDF4LLMdual: AGPL 3.0 or commerciallocal, CPUoptional enginesmarkdown table on this corpus, columns interleaved0
MarkItDownMITlocal, CPUnone for PDF by default, optional cloud backendflattened column-by-column on this corpus0
DoclingMITlocal, CPU or GPUbuilt-inaligned markdown table on this corpus0
MarkerApache 2.0 code, separate model-weights licenselocal, GPU preferred, CPU supportedbuilt-in modelstrong per docs and secondary testing0 local; hosted 4 fast, 10 accurate
MinerUApache 2.0 with extra termslocal, CPU pipeline or GPU VLM backendbuilt-in, 109 languagesstrong per docs and secondary testing0
LlamaParsecommercial, cloudcloudbuilt-inbuilt-in, extraction modesfrom 1.25 basic tier
Mistral OCRcommercial, cloudcloudbuilt-inmarkdown tables inline per docs4

License fine print worth money: PyMuPDF4LLM's dual license means AGPL 3.0 unless you buy the commercial license, which one practitioner on a web-dev forum flagged as the reason it is "not free for commercial use" for his team. Marker's model weights ride a license separate from its Apache 2.0 code, free below a startup funding or revenue threshold, commercial above it. MinerU is Apache 2.0 plus terms below very large scale, with an attribution obligation if you serve it online.

What parsing costs per page at volume

LlamaParse bills credits: 1,000 credits cost 1.25, and basic parsing runs "as low as 1 credit" per page by their pricing FAQ, which puts the basic tier at 1.25 per 1,000 pages (LlamaIndex pricing). Accuracy tiers cost more credits per page, and plan caps matter at volume: free and starter plans allow 5 concurrent parse jobs, pro 20, enterprise 100. A team on a data-engineering forum hit exactly that ceiling: "throttled at 5-10 concurrent parsing jobs", a "major problem for us". The July 2026 thread that went looking for a cheaper route put the trade in one line: LlamaParse was "the best quality I've tested, but the cost doesn't scale for my volume", in a corpus of notarial documents and 400-to-600-page books where "tables matter". Re-parsing a cached result costs zero credits, and cached data is retained 48 hours unless caching is off.

Datalab, Marker's maker, sells hosted parsing on a per-processor rate card: document conversion lists at 4 per 1,000 pages in the fast mode and 10 in the accurate mode, with a free monthly allowance and a team plan at 400 per month (Datalab pricing). The consumption model is itself news of a sort: the 2024 forum thread that made Marker popular also carried the complaint that it was "forcing monthly subscription" when the poster wanted "consumption based API". The current card is consumption-based. Prices move; date every number you plan against.

Mistral OCR lists 4 per 1,000 pages for the current model in the official OCR announcement dated 23 June 2026 (Mistral OCR news post), with a 50 percent batch discount on the same page. For a community-side anchor, one forum reply in 2024 put Mathpix, a formula-strong commercial parser, "at about 1 cent per page".

The build-versus-buy arithmetic is the same as every infrastructure decision: pages per month times price per page on one side, engineering hours times your rate plus the maintenance tail on the other. The local tools cost zero per page and a nonzero number of weekends. The metered services cost a number you can multiply before you commit. For budgeting metered spend of any kind, the platform's pricing page keeps current per-unit rates in one place.

Markdown then chunk, or chunk the PDF?

Chunk the markdown. The question sounds symmetric and is not. A PDF page is a print artifact: its natural unit has no semantic meaning, so a chunker over raw PDF text cuts characters at arbitrary offsets, across table rows and column boundaries, with running headers folded in. The markdown intermediate gives the chunker real boundaries:

def chunk_markdown(text):
    chunks, cur = [], []
    for line in text.splitlines():
        if line.startswith("#") and cur:
            chunks.append("\n".join(cur))
            cur = []
        cur.append(line)
    if cur:
        chunks.append("\n".join(cur))
    return chunks

Split at headings, keep the heading line inside its chunk so the chunk carries its own context, leave tables whole, and strip the furniture before embedding, either with your parser's switches or before the chunker runs. If your sections run long, cap chunk size with a second split inside the section, not instead of it. If your pipeline has no markdown stage today, that stage is the least expensive upgrade available to it, and the next section is how you prove it instead of taking this page's word.

Did the parse actually help retrieval? Measure it

A retrieval-engineering forum thread asked the question no ranking page on this topic answers: how much meaning survives the PDF to markdown conversion. The honest answer is that you measure it, and the measurement is cheap. The exhibit below ran in a plain Python container with no API keys: a fixed question set with gold answers over the corpus, three chunking variants built from the captured outputs above, and a lexical scorer. Nothing in the protocol depends on the scorer; swap in your embedding model and vector store and the shape is identical.

The twelve questions are the kind a user of the corpus document would ask, each pinned to a gold span, for example "What is the service limit for a class C impeller?" with the gold span 0.35, and "Which station is the only one with class D impellers?" with the gold span P-407, which lives only in the footnote. Three variants:

  • flat: the MarkItDown output, fixed 400-character windows, no structure
  • md-headers: the PyMuPDF4LLM default output, split at headings, furniture kept
  • md-clean: the same output with header=False, footer=False, furniture gone

Scored with BM25, hit at rank 1 and rank 3, from the captured run log:

VariantChunkshit@1hit@3
flat, fixed windows137 of 129 of 12
markdown, furniture kept711 of 1212 of 12
markdown, furniture stripped611 of 1212 of 12

Three findings worth more than the totals. First, both table questions missed entirely in the flat variant, at any rank: the column-by-column serialization scattered the values across window boundaries, so the chunk holding 0.35 no longer held the words that made it the class C answer. Structure was the difference between retrieving a number and retrieving an answer, and no retriever tuning fixes that. Second, the markdown variants' single miss at rank 1 is the footnote question, in both: the appendix chunk mentions class D impellers too and outranked the note. Docling's fused appendix heading is a small defect with a visible retrieval cost; hunt heading losses on your corpus for that reason. Third, the two markdown variants scored identically here, the honest result: on three clean pages, furniture removal did not move lexical retrieval. It moves more as documents get shorter and furniture denser, and it always moves token count.

To run this on your own stack: take the question set from real user questions, pin each gold span by hand, chunk each parser variant your way, retrieve with your actual retriever, and read hit at 1 and 3 first. The forum's scale advice stands: 20 to 50 real documents before you commit a pipeline. The scorer is run_retrieval_eval.py and regenerates the table above.

Private documents stay local

When the corpus cannot leave your network, notarial records, patient documents, contracts, the local parsers run with no outbound calls after install. Every exhibit in this guide was produced that way: tools installed into a throwaway container, then run over a local file. Docling and PyMuPDF4LLM need no account, no key, no upload. MarkItDown is the same for PDFs unless you point it at a cloud OCR backend deliberately.

For the cloud services, take their data handling from their own documents. LlamaParse's pricing FAQ states that parsed results used for caching are retained 48 hours, that caching can be turned off, and that enterprise deployments can run in a private VPC. Mistral and Datalab publish their own retention and compliance terms; read those pages before a sensitive corpus touches either. No boundary claim here goes beyond a vendor's own docs, and no parser choice substitutes for your compliance review when the documents are regulated.

When to stop running your own parsing stack

The signals are concrete. New document shapes break your pipeline monthly. Your repair layer grows faster than your test set. Someone has re-typed a table to make a deadline. Your eval numbers drift and you cannot say which document family caused it. At that point the parsing stack has become a product you maintain, and the question is whether it should be your product.

A managed parsing route is the alternative: a per-page price you can multiply, no model operations, and an output contract you can hold to the standard this guide demonstrates, captured outputs you can check on your own documents. This platform's document parsing lane is on the roadmap: per-page parsing of PDFs and images into structured text is being built, and when it lands it will sit behind the same prepaid wallet and the same OpenAI-compatible endpoint family that serve the platform today. The wallet model is the part worth knowing now: every call is metered against a balance you control, and a key whose balance reaches zero is denied at request time, a production behavior verified 11 September 2026. The worst case a parsing runaway can produce is a wallet that needs a top-up, not an unplanned bill. Per-key spend caps, which several parsing vendors offer, are worth wanting from any provider as an industry practice; the wallet floor is the hard stop here today. Current rates for every lane sit on the pricing page, a wallet and key take minutes on the signup page, and runaway metered spend has its own guide: how to avoid API bill shock. The break-even treatment in TTS local vs API break-even shows the build-versus-buy arithmetic, and the OpenAI-compatible API guide shows the endpoint family a parsing lane joins when it lands.

Frequently asked questions

How do I turn a PDF into RAG?

Convert the PDF to markdown with a parser, strip the running headers and footers, chunk the markdown at its headings with tables kept whole, embed the chunks, and store them with their heading text as metadata. Then answer questions by retrieving the top chunks and passing them with the question to your model. The pipeline is parse, chunk, embed, retrieve, generate, and the parse step sets the ceiling for everything after it.

Which PDF parser is best for RAG in 2026?

No universal winner, and the community's own answer is a test on your corpus. The 2026 shortlist the forums converge on: Docling, MarkItDown, Marker, MinerU, Unstructured, and the cloud services LlamaParse and Mistral OCR. The defaults that fall out of testing: MarkItDown for simple born-digital files where speed dominates, Docling when structure matters and you can pay the runtime, Marker with a GPU for math-heavy documents, a cloud service when volume justifies a per-page bill. Run the showdown method from this guide on 20 of your own documents first.

Docling vs MarkItDown vs Marker: which one?

MarkItDown is the fast, structure-blind option: sub-second per document here, but flattened tables and scrambled reading order in the same run. Docling is the slow, structure-first option: 29 seconds for three pages on CPU, but the only capture with correct reading order and clean furniture handling. Marker sits in the accuracy-first tier with a GPU preference, strongest on math and figures per its documentation and independent testing. The three embody one trade, speed against structure, and the retrieval section shows why structure usually wins for RAG.

How do tables and multi-column layouts survive PDF to markdown?

It depends entirely on the parser, and the survival is visible in the outputs above. The ruled table survived as a markdown table under PyMuPDF4LLM and Docling, and was serialized column by column under MarkItDown, which preserves every value while destroying the mapping between value and row. Two-column text interleaved under PyMuPDF4LLM's default ordering and came out correct under Docling. Merged cells and nested headers are the harder case: the merged-cell extraction guide shows those failures tool by tool.

What does PDF parsing cost per page at volume?

Local parsers cost no per-page fee; you pay hardware and maintenance. On the metered side, list rates read 11 September 2026: LlamaParse's basic tier works out to 1.25 per 1,000 pages at one credit per page, with accuracy tiers costing more credits; Datalab's hosted conversion lists at 4 per 1,000 pages fast and 10 accurate; Mistral OCR lists 4 per 1,000 in its official announcement. The full table with sources is above. The merged-cell extraction guide carries the same math for the table-repair lane.

How do you measure whether parsing helped retrieval?

Fix a question set with gold answers, chunk each parsing variant, retrieve with your real retriever, and compare hit rates at rank 1 and 3. The runnable version in this guide: twelve questions over a three-page corpus, three chunking variants, BM25 scoring, and the result that the flat variant missed both table questions while the markdown variants answered them. Swap in 20 to 50 real documents, your embedding model, and your vector store, and keep the gold spans hand-pinned so the metric means something.

Are paid parsers noticeably better than open source?

On clean born-digital documents the gap is small and shrinking: Docling's free capture was the best output in this guide's showdown. The paid services earn their per-page price on the hard tail, scans, handwriting, dense multi-column filings, and on operations: concurrency, throughput, and not running model servers. Paid tools are noticeably better exactly where your documents are hardest, which is why the advice is always the same: test on your hardest twenty documents, and price the winner per 1,000 pages.

Last verified: 2026-09-11. Tool versions for the captured exhibits: pymupdf4llm 1.28.2, markitdown with PDF extras, docling 2.126.0, reportlab 5.0.1, Python 3.12.13, run in containers on 11 September 2026; the corpus, parser runner, and retrieval scorer ship alongside this guide so every output and number above can be regenerated and checked.