survos / llama-cpp
llama.cpp models inside the PHP process through FFI, and OCRonos-Vintage OCR correction on top of it. No HTTP, no Python.
Fund package maintenance!
Requires
- php: ^8.5
- ext-mbstring: *
Requires (Dev)
- phpunit/phpunit: ^13.0
Suggests
- ext-ffi: Required to run a model: llama.cpp is called through FFI.
Provides
None
Conflicts
None
Replaces
None
README
llama.cpp models inside the PHP process, through FFI, and OCR post-correction with OCRonos-Vintage built on it. No HTTP service, no Python.
use Survos\LlamaCpp\LlamaModel; use Survos\LlamaCpp\Ocronos\Corrector; $model = new LlamaModel('/usr/local/lib/libllama_bridge.so', '/var/lib/models/ocronos-vintage-q8_0.gguf'); $corrector = new Corrector($model); $done = $corrector->correct('Tne bride was dre.ssed in her mo.3t gallant attire.'); $done->text; // The bride was dressed in her most gallant attire. $done->truncated; // false: the model finished $done->lengthIsSuspect(); // false: about as long as the source $corrector->correct($page, segment: true); // longer text, corrected a segment at a time
bin/ocronos "Tne Board of Superv1sors met at tbe Court Houfe on Monday laft." bin/ocronos --segment < page.txt
What is in it
| Class | Does |
|---|---|
LlamaModel |
One GGUF model: countTokens(), generate() (greedy: the same prompt always gives the same text), loads(). Loads on first use and stays loaded. |
Ocronos\Corrector |
The correction prompt, the 480-token input limit, segmenting of longer passages (paragraph, line, sentence, word), joining with the source's own separators. A port of ai-tools' cv/text_correction.py. |
Ocronos\Correction |
The text, token counts, truncated, and lengthIsSuspect(). |
TextModelInterface |
What the corrector needs from a model, so it can be tested without one. |
Fidelity
Checked against ai-tools' PyTorch implementation (the reference, at the same pinned model revision) on nine passages, one of them long enough to be segmented: the corrected text, the token counts and the segment boundaries are identical. That held for all four files tried:
| File | Size | Eight passages |
|---|---|---|
| f32 (lossless) | 500 MB | 2.0 s |
| f16 | 252 MB | 1.5 s |
| q8_0 | 136 MB | 0.7 s |
| q4_k_m | 91 MB | 0.9 s |
PyTorch took 3.9 s for the same eight on the same machine (Apple M4, CPU). Nine passages is a small sample:
quantised files can differ on other text, so compare on your own before relying on one. q8_0 is the suggested one.
What to know about the model
- It rewrites. It modernises spelling ("to-day" becomes "today") and can change a name it does not know. Keep the source OCR and store the correction beside it, never over it.
- It continues patterns. Given the same paragraph twelve times it wrote it sixteen times.
lengthIsSuspect()flags a correction whose length is more than a quarter away from its source's. - It is small: a prompt of at most 480 tokens and a 1,024-token context. Without
segment: truea longer passage is refused, never cut short. truncatedmeans the model ran out of room before finishing: treat that text as incomplete.
Setting up
- llama.cpp as a shared library with its header:
brew install llama.cppon macOS; on Linux build it with-DBUILD_SHARED_LIBS=ON. - The bridge:
bin/build-bridge.sh [output-directory]compilesbridge/llama_bridge.cppagainst it. PHP calls this small C library rather thanllama.hdirectly, becausellama.hpasses large structs by value and changes them between releases. - The model:
bin/convert-ocronos.sh <llama.cpp source checkout> [output-directory]downloads the pinned OCRonos-Vintage weights (Apache-2.0) and writes the f32 and q8_0 GGUF files. It needs Python with torch and transformers once, for the conversion only.
PHP needs the FFI extension, and ffi.enable=1 under a web server (the command line always has FFI). Keep the
LlamaModel for the life of the process (a FrankenPHP worker, a queue consumer): one load, about 0.2 s, serves
everything after it. Verified on macOS arm64 with llama.cpp 0.9.0; Linux has not been tried.