Skip to content

1.8.0

Choose a tag to compare

@nullata nullata released this 24 Aug 19:20
· 104 commits to main since this release

[1.8.0] - 2026-08-24

Added

  • Image & PDF Input. llama-server has no PDF support at all — a PDF sent as an image_url data URL, an OpenAI file block with inline file_data, or an Ollama images[] entry simply returns a base64/decode error, and every OpenAI-compatible layer downstream of it inherits that gap. This release closes it. When an instance opts into Image & PDF Input (renamed from "Image Input") in the launch form and points MMPROJ Model at a multimodal projector GGUF, any inbound PDF is rewritten before llama-server sees the request — into an image_url block per rasterized page (default; needs the vision model + mmproj) or, when Try text layer first is on, into a single text block carrying the PDF's embedded text if it's substantive enough to trust (threshold: 50 chars/page averaged over the pages actually looked at). The text-layer path is a free win for born-digital PDFs (Word/LaTeX/browser print) and completes in milliseconds with no raster pass or model context spent on pixels; scanned PDFs fall through to rasterization automatically, so turning it on never breaks the OCR case. It is nevertheless default off, because "sometimes text, sometimes N images" is a surprising per-request shape shift for a caller who thought they were sending a picture — leaving it off gives predictable behavior (always rasterize) and operators opt in per instance when their traffic is text-heavy PDFs.
    • Both compat surfaces: /v1/chat/completions accepts either the OpenAI image_url block with an application/pdf data URL, or the OpenAI file block with inline file_data — MIME is header-sniffed (%PDF-) rather than trusted from the label. /api/chat and /api/generate accept PDFs inside the Ollama-native images[] array on any message; the translator splits real images from PDFs, lifts the images through the existing image_url path, and inlines the PDF's expanded blocks alongside. Broken PDFs surface as 400 with the specific decode error, not an opaque 500 from llama-server.
    • Per-request caps, not global: PDF DPI (72–600, default 200) and Max PDF Pages (1–200, default 20) are preset fields so operators bound the cost per instance. A PDF above max_pages is refused up front before any raster work — so a 500-page whitepaper cannot monopolize the raster pool for minutes. All four PDF keys are require-relaunch (unlike sampling knobs), because they participate in launch args and the mmproj path check.
    • LLAMAMAN_PDF_MAX_CONCURRENT env var (default 4) caps how many rasterizations can run process-wide at once. This is a process-level BoundedSemaphore, deliberately independent of any per-instance RequestGate: rasterization is CPU-heavy and spikes a few hundred MB of transient RAM per page render, and the RequestGate only limits inference concurrency downstream. A burst of PDF traffic without this cap would peg cores and thrash swap before the gate ever saw a request. Raise on beefy hosts with PDF-heavy traffic, lower on tight ones. Overflow blocks on the semaphore rather than piling on load.
    • Stack: poppler-utils (added to the Docker image) + pdf2image for rasterization, pypdf for the text-layer probe, Pillow for the PNG encode. All permissive licenses; PyMuPDF (AGPL) was considered and rejected to keep license semantics simple for downstream users.
    • UI: the launch form's Image & PDF Input section adds Accept PDF uploads, Try text layer first, PDF DPI, and Max PDF Pages underneath the existing MMPROJ Model control; the toggles gate the numeric inputs client-side, and the server re-validates bounds. Preset save/load, restart, and the auto-launch preset-merge whitelist all carry the four new keys.
    • Migration: none. All four preset keys default to safe values (pdf_input_enabled: false), so existing installs behave identically until an operator turns the feature on per instance. 23 new tests in tests/test_pdf_input.py cover the header sniff, text-layer heuristic, page-cap enforcement, OpenAI and Ollama block expansion, and the preset validator.

Fixed

  • Sleeping models could jam LLAMAMAN_MAX_MODELS and refuse to wake on OpenAI-compat requests. With MAX=2 and two sleeping instances, an OpenAI request for one of them returned HTTP 503: model limit reached; the OpenAI API does not evict running models — instead of doing the obvious thing and waking it. The Ollama-compat path could hit the same 503 whenever Allow Ollama API to evict admin-launched models was off and the cap was full of admin-launched sleepers. Root cause was structural: _ensure_model_running ran the cap check before it knew whether the request would just wake an existing sleeper or actually launch a new one, and treated both identically. The fix computes would_wake_existing up front from the resolved existing record and skips both the cap check and the eviction pass when it's true. Waking a sleeping instance is using the slot that instance was already granted at launch — sleep pauses the container to free VRAM/RAM, but the launcher's authorization for that slot persists across the pause; only creating a genuinely new instance consumes a new slot.
    • Deliberately preserved: sleeping still counts against the cap for fresh launches, so an API request for a different model, when the cap is saturated by admin-launched sleepers, still returns 503 unless the "Allow API to evict admin-launched" toggle is on. That's the entire reason those toggles exist — the admin's slot claim has to survive a sleep/wake cycle or the toggles would be meaningless the moment idle timeout fires. The _count_running_* helpers therefore keep counting sleeping instances (six lines of docstring, no behavioral change to counting); only the wake path skips the check.
    • Applies to: both compat surfaces (/v1/chat/completions, /api/chat, /api/generate) — they share _ensure_model_running, so the fix reaches everything driven by the proxy. Direct hits to a per-instance public port were never affected because they don't cap-check; only the auto-launch/wake path did.
    • Docs: README.md §"Model eviction policy" gains a bullet spelling out the wake-vs-launch distinction explicitly, so a reader inferring behavior from the cap docs alone doesn't reach the old (buggy) expectation. Covered by 6 regression tests in tests/test_sleeping_not_counted.py.