Capabilities

Images, scans and video

A vision model can read a picture in a chat, a scanned PDF page by page, or a short clip as a handful of timed frames — and answer in a JSON Schema if you ask. It all runs on this machine, on the CPU, and it is slow: plan in minutes per page, not seconds.

What you need

A vision model is two files: the language model, and its vision projector (an mmproj GGUF) that turns an image into positions the model can read. Both come from the same repository.

llamay pull hf:ggml-org/gemma-3-4b-it-GGUF/gemma-3-4b-it-Q4_K_M.gguf -as gemma3-4b-vision
llamay pull hf:ggml-org/gemma-3-4b-it-GGUF/mmproj-model-f16.gguf -as gemma3-4b-mmproj
llamay serve -m gemma3-4b-vision -mmproj gemma3-4b-mmproj -gpu=false

A picture in a chat

Send the image inline, in whichever shape your API uses: an OpenAI image_url part with a data: URI, an Anthropic image block, or Ollama's images list.

import base64
from openai import OpenAI

client = OpenAI(base_url="http://localhost:11435/v1", api_key="unused-on-loopback")
png = base64.b64encode(open("invoice.png", "rb").read()).decode()

reply = client.chat.completions.create(
    model="gemma3-4b-vision", temperature=0, max_tokens=60,
    messages=[{"role": "user", "content": [
        {"type": "text", "text": "Who is the supplier and what is the total? One line."},
        {"type": "image_url", "image_url": {"url": "data:image/png;base64," + png}},
    ]}],
)
print(reply.choices[0].message.content)
Acme Ltd, 1,284.50 EUR

That took 102 seconds on an Apple M4 CPU, almost all of it the vision tower.

Scanned PDFs, into a schema

A scanned PDF is a picture of each page in an envelope, and it is read as one. Send it as an OpenAI file part, an Anthropic document block, or a data:application/pdf URI. Add response_format and the answer is JSON that matches your schema, enforced token by token.

curl -s localhost:11435/v1/chat/completions -d '{"model": "gemma3-4b-vision",
  "messages": [{"role": "user", "content": [
    {"type": "file", "file": {"filename": "invoice.pdf", "file_data": "data:application/pdf;base64,JVBERi0x..."}},
    {"type": "text", "text": "Extract the invoice."}]}],
  "response_format": {"type": "json_schema", "json_schema": {"name": "invoice", "schema": {
    "type": "object", "required": ["invoice_number", "total"],
    "properties": {"invoice_number": {"type": "string"}, "total": {"type": "number"}}}}}}'

From the command line

llamay ocr -image reads a picture or a scanned PDF with the same path. It prints the text by default, or JSON with -schema:

llamay ocr -image invoice.pdf -m gemma3-4b-vision -mmproj gemma3-4b-mmproj \
  -schema invoice.schema.json -stats
{"invoice_number":"4471","supplier":"Acme Ltd","total":1284.50,"currency":"EUR"}
read 366 positions (256 of them image) in 1m19.946s · generated 31 in 27.774s · stop: stop
FlagDoes
-imageThe picture or scanned PDF to read.
-pageWith a PDF, read only this page (from 1). 0 reads every page.
-schemaA JSON Schema, as a file or inline: extract into it instead of transcribing.
-pAsk this instead of the default transcription prompt.
-lexiconSnap the text (or the schema's string values, never its keys) to a word list.
-video, -video-frames, -fps, -max-framesRead a clip instead; see below.

llamay ocr -frames is a different tool: it decodes the output of a CTC text recogniser, optionally against a lexicon. See the CLI reference.

Video, as timed frames

No projector llamay serves was trained on video, so a clip is read the way a person reads a contact sheet: a few frames, in order, each labelled with its time. Send a video_url part holding a data: URI, or the frames you cut yourself as {"type": "video", "video": ["data:image/png;base64,...", ...]}.

llamay ocr -video lecture.mp4 -fps 0.2 -max-frames 12 -m gemma3-4b-vision -mmproj gemma3-4b-mmproj

Limits, stated plainly