llm.pdf: a language model that lives inside a PDF file
Run LLMs inside a PDF file.
At a glance
- What is it?
- llm.pdf is a proof of concept that compiles llama.cpp to asm.js with Emscripten and runs the result inside a PDF through an old PDF JavaScript injection, with the model weights embedded as base64. It is a demonstration of a trick, not a usable inference setup, and the README frames it that way.
- Who is it for?
- Adopt llm.pdf if your goal is to understand what a PDF JavaScript injection can do, or to produce a self-contained artefact for a demo where a single file that opens anywhere is the point, because that is the trick the project demonstrates. Do not adopt it for real inference, because the README's own guidance is that models above 135 million parameters will be unreasonably slow, and a 5 second token is a demonstration rather than a service.
- Can I use it commercially?
- Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
- Is it still maintained?
- Yes. The repository last received commits 40 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A proof of concept, and the README does not pretend otherwise
llm.pdf is described in one line: run LLMs inside a PDF file. The project page is at evanzhoudev.github.io/llm.pdf, and there is a video on YouTube that walks through how the artefact was built.
The README is unusually direct about the status of the work. It calls it a proof-of-concept project showing that it is possible to run an entire Large Language Model in nothing but a PDF file. That framing is the right one to hold on to, because the interesting part is the demonstration, not the resulting tool. Nothing in the repository claims a usable inference setup, and the model guidance further down the README says as much in effect, by telling you that anything above a very small model will be unreasonably slow.
The implementation language is Python, the default branch is main, and the repository listing records no licence, which is worth noting on its own for a project that distributes binaries and third-party model weights.
The last push was on 2026-09-13 and there are no GitHub releases, so the artefact is not versioned as a downloadable package. The repository itself carries a builds/ directory, a website/ directory, a scripts/ directory, an assets/ folder with the logo, a src/ directory, a llama/ directory, a mise.toml file for tool versions, and a requirements.txt with a single dependency, pdfrw.
That last entry is a small clue about the pipeline. pdfrw is a Python library for reading and writing PDF structures, and a script that uses it is a script that is assembling a PDF file, not one that is running a model.
The mechanism is asm.js plus a PDF JavaScript injection
The technical trick has two halves, and the README states both.
The first half is the compiler. Emscripten is used to compile llama.cpp into asm.js, the historical WebAssembly predecessor that runs as JavaScript. The second half is the host. That asm.js is then run inside the PDF using an old PDF JS injection, which is a technique for getting a PDF viewer to execute JavaScript embedded in the document itself. The PDF specification has a JavaScript action type, and the project uses it as the execution environment for the model.
The third piece is the payload. Embedding the entire LLM file into the PDF with base64 is what makes the document self-contained, so the result is a single file that carries both the inference engine and the weights, and the README states that this combination is what allows inference to happen in nothing but a PDF.
The practical implication of the design is worth spelling out, because it is the reason the trick is interesting. There is no server in the loop and no network dependency at inference time. The file is the model, and opening the file is the computation.
What that costs is equally worth spelling out. The weights are base64 encoded, and base64 inflates binary payloads by roughly a third, so the document carries noticeably more bytes than the model itself. And a JavaScript action in a PDF is not a normal JavaScript host: it is a restricted environment inside a viewer whose behaviour differs between implementations, which is why the README calls the injection old and why the supported viewers are not documented. A model that runs in a browser tab may not run at all in a PDF reader that ships with a document management system, and the README does not say which readers work.
Model choice is the binding constraint, and the ceiling is low
The README gives explicit guidance on picking a model, and it is the section that decides whether a reader can use this for anything.
Only GGUF quantized models work. That rules out the original model formats and means you are working with a quantized checkpoint. The guidance then says to generally use Q8 quantized models, as those run the fastest. Finally, for reference, 135M parameter models take around 5 seconds per token of input or output, and anything higher will likely be unreasonably slow.
Those numbers set the ceiling for the whole project. A model with 135 million parameters is a very small model, on the scale of the tiny language models the project credits. The README thanks the authors of the EleutherAI pythia models, of Ronen Eldan and Yuanzhi Li's TinyStories LLM, and of arnir0's Tiny-LLM, for the tiny models that power llm.pdf. So the demo runs on models that are interesting as research artefacts and as test fixtures rather than as assistants.
At five seconds per token, the useful output of a passing inference is a sentence, not a document. That is fine for a demonstration and painful for anything else, and the number also tells you where the time goes: the whole thing runs as JavaScript in a PDF's script engine, a far more limited host than a browser tab, with no GPU and none of the browser-side optimisations asm.js normally benefits from.
So the right way to read the project is as a measurement of how much a PDF JavaScript host can do, and as a constraint demonstration. The README's own model guidance is the honest summary of where it lands.
Building a PDF with a model of your own is one script
The README points at a single script for the build step, scripts/generatePDF.py, which creates a PDF containing any compatible model, and gives the command to start:
cd scripts
python3 generatePDF.py --model "path/for/model.gguf" --output "path/to/output.pdf"Two arguments, a path to a GGUF file and a path to the output, which is a good sign for a proof of concept: the complexity of the pipeline is hidden behind a script rather than spread across instructions.
The dependency list is one line, pdfrw, which is consistent with a Python script that assembles the document. Everything else needed to produce the runnable artefact, the Emscripten compilation of llama.cpp to asm.js and the JavaScript injection itself, lives in the repository's llama/, src/ and builds/ directories, and the mise.toml file suggests the toolchain versions are pinned through mise so that the build is reproducible on another machine.
The two ideas the project credits are worth naming, because they show where the trick comes from. ading2210's DoomPDF does the same kind of thing with a game, running a game inside a PDF, and rahuldshetty's llm.js runs a language model in the browser. llm.pdf combines the two: a browser-style language model, moved into a PDF's JavaScript host, with the model file inlined rather than fetched.
A practical note for anyone who wants to reproduce it. The output is a single file, so the failure mode is usually the viewer rather than the model. If nothing happens when you open it, the first thing to check is whether your reader executes PDF JavaScript at all, since the README does not name a supported viewer list.
What the repository does not settle, and what a licence would have to cover
Three questions a reader is left with are worth listing plainly.
The first is the licence. The repository listing records the licence as unknown, and this is a project that would need one. It compiles llama.cpp, whose licensing is separate from this repository's, and it ships a copy of a third party's model weights inside the artefact, and the weights of the tiny models it credits come from their own authors with their own terms. A licence for the repository would not settle any of that, which is exactly why the question needs an answer rather than an assumption.
The second is the viewer matrix. The README says the asm.js is run in the PDF using an old PDF JS injection, and does not say which readers implement it, whether Acrobat and the browser viewers behave the same, or what happens in a reader that blocks scripting by default, which is a common setting in managed environments.
The third is the build reproducibility for anything beyond the bundled artefact. The Emscripten compilation step means the artefact depends on a toolchain, and the repository pins tool versions through mise.toml rather than describing them in prose, so the practical way to know what produced a given PDF is to look at the build configuration rather than at a changelog.
None of that is a criticism of a proof of concept, which is not trying to be a product. It is a description of the boundary. The project demonstrates a genuine and slightly absurd capability, publishes the artefacts and the code, links the video that explains the build, and states plainly that it is a proof of concept. The last push was on 2026-09-13, and there are no GitHub releases, so the practical way to consume it is to take the repository and the artefacts as they stand.
Editorial conclusion
Adopt llm.pdf if your goal is to understand what a PDF JavaScript injection can do, or to produce a self-contained artefact for a demo where a single file that opens anywhere is the point, because that is the trick the project demonstrates. Do not adopt it for real inference, because the README's own guidance is that models above 135 million parameters will be unreasonably slow, and a 5 second token is a demonstration rather than a service. Before you build anything on it, check two things the README leaves open: which PDF viewers actually execute the injected script, since the technique is described as an old injection, and what licence applies, because the repository listing records no licence and the project ships code from Emscripten, llama.cpp and a base64 copy of someone else's model weights.
Frequently asked questions
What is llm.pdf and how does it run a model inside a PDF?
It is a proof-of-concept project that uses Emscripten to compile llama.cpp into asm.js, runs that inside the PDF with an old PDF JS injection, and embeds the entire model file as base64. The README states that the result is LLM inference in nothing but a PDF, with no server involved.
How do I build a PDF with my own model?
The README points at the scripts/generatePDF.py script, and the command is cd scripts followed by python3 generatePDF.py --model "path/for/model.gguf" --output "path/to/output.pdf". The Python dependency for the script is pdfrw, which is the only entry in the repository's requirements.txt.
Which models work with llm.pdf?
Only GGUF quantized models work, and the README advises Q8 quantization because it runs the fastest. It gives 135M parameter models at around 5 seconds per token of input or output as a reference point and says anything higher will likely be unreasonably slow.
Which PDF viewers can run llm.pdf?
The README describes the technique as an old PDF JS injection and does not name supported viewers. It does not document the viewer matrix, so if the file appears to do nothing, the reader's support for PDF JavaScript is the first thing to check.
What licence does llm.pdf use?
The repository listing records the licence as unknown. The project compiles llama.cpp and embeds third-party model weights, the tiny models credited being the EleutherAI pythia models, the TinyStories LLM and arnir0's Tiny-LLM, so the terms for the artefact and the code are not settled by the repository alone.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/evanzhoudev-llm-pdf)Was this useful?