You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.