Vision, Speech and Documents: Multimodal Locally
Most of the material you would actually like a model to work with is not plain text. It is a photograph of a whiteboard, an hour of a meeting, a scanned invoice, a screenshot of an error. By the end of this lesson you will be able to send each of those to a model on your own machine, say what each one costs in context and memory before you send it, and state which engine supports which modality on your track rather than finding out by failing.
An image becomes tokens
Section titled “An image becomes tokens”There is one mechanism under all of this, and knowing it removes most of the surprises.
A multimodal model is a language model with an extra encoder bolted on the front. The encoder turns an image, or a slice of audio, into a sequence of vectors, a projector maps those vectors into the same space the language model’s token embeddings live in, and from that point on they are just tokens in the context. The model does not have a special image input; it has a longer prompt.
What happens to a picture on the way into the model
- Your image or audioA file, a base64 data URL, or a path on the machine running the server.you supply this
- EncoderA vision or audio tower. Separate weights from the language model, often a separate file.
- ProjectorMaps the encoder output into the language model's embedding space. In llama.cpp this is the mmproj file.
- ContextThe result occupies context positions exactly like text tokens, and costs key-value cache exactly like text tokens.this is the bill
- Language modelPrefill and decode as usual. Nothing downstream knows the tokens came from a picture.
Three practical facts fall out of that diagram.
An image has a token cost, and it is not small. A page-sized image at a resolution high enough to read the small print is worth a substantial fraction of a short document. Several images in one conversation add up quickly, and the failure looks like a context overflow, not like an image problem.
The encoder is extra weights. In llama.cpp it is a separate GGUF, the multimodal projector, that you load alongside the model. It has to fit in memory too.
Resolution is a dial you control. Sending a smaller image costs fewer tokens and reads less small print. For “what is on this whiteboard” a small image is fine; for “read this invoice number” it is not, and choosing per task rather than globally is the difference between a usable pipeline and an expensive one.
Qwen3-VL-8B at Q4_K_M with an 8k context, on a machine with 8 GB available to the model
- Weights (Q4_K_M)
- 5.4 GB
- KV cache, 8k context
- 1.2 GB
- Free
- 1.4 GB
- Total
- 8 GB
Vision, by engine
Section titled “Vision, by engine”llama.cpp documents multimodal input through a library it calls libmtmd, stating that
“llama.cpp supports multimodal input via libmtmd” and naming the tools that use it:
llama-cli, llama-server through the OpenAI-compatible chat completions API, and
llama-mtmd-cli. The model and its projector can be loaded together: with -hf the
projector is downloaded for you, and --mmproj points at a local projector file when you
have your own. --no-mmproj-offload keeps the projector on the processor when video memory
is scarce, which is a useful lever on an 8 GB card.
RunnableAll tracks
llama-server \ --hf-repo ggml-org/Qwen2.5-VL-7B-Instruct-GGUF:Q4_K_M \ --alias vision \ --ctx-size 8192 \ --host 127.0.0.1 \ --port 8082RunnableAll tracks
llama-mtmd-cli \ --model ~/models/vision/model-Q4_K_M.gguf \ --mmproj ~/models/vision/mmproj-f16.gguf \ --image ~/pictures/whiteboard.jpg \ --prompt "Transcribe the text on this whiteboard. If a word is unreadable, write [unclear]."Qwen3-VL-8B-Instruct is this course’s reference vision model: Apache-2.0, a native context of 256K tokens which its card says is expandable to 1M, optical character recognition that the card describes as covering 32 languages, document structure parsing, video understanding, and operating graphical interfaces well enough to be used as a screen agent, which is where Part 26 picks it up.
vLLM takes multimodal data in two shapes. Offline, a multi_modal_data dictionary
alongside the prompt, keyed "image", "audio" or "video". Online, through the
OpenAI-compatible chat API, where an image is a content item of type image_url whose URL
may be a base64 data URL, audio arrives as input_audio or audio_url, and video as
video_url. --limit-mm-per-prompt caps how many items of each modality a request may
carry, which is a control worth setting before anybody else can reach the endpoint.
MLX has mlx-vlm, described as “a package for inference and fine-tuning of Vision Language
Models (VLMs) and Omni Models (VLMs with audio and video support) on your Mac using MLX”,
MIT-licensed, installed with pip install -U mlx-vlm. It provides a generation command, a
server and a chat interface, and lists Qwen among more than thirty supported architectures.
Speech to text
Section titled “Speech to text”Two families matter for local work, and the course pins one of them.
Parakeet is the course’s reference. parakeet-tdt-0.6b-v3 is a 0.6B model that its card
describes as a multilingual automatic speech recognition model for high-throughput
transcription, with a FastConformer encoder and a TDT decoder, automatic punctuation and
capitalisation, and word-level and segment-level timestamps. It covers 25 European
languages and detects the language itself without prompting. It expects 16 kHz mono audio in
.wav or .flac. The card reports handling audio up to 24 minutes with full attention on an
A100 80GB, or up to three hours with local attention; treat those as the publisher’s figures
for their hardware, not yours.
It runs through NVIDIA’s NeMo toolkit, whose ASR documentation shows the shape of the call:
Fragment — not complete on its own
import nemo.collections.asr as nemo_asr
asr_model = nemo_asr.models.ASRModel.from_pretrained("nvidia/parakeet-tdt-0.6b-v3")transcript = asr_model.transcribe(["meeting.wav"])[0].textWhisper-class models are the other family, and they are not in this course’s model table because the course pins Parakeet. They are worth naming because you will meet them everywhere: whisper.cpp describes itself as “high-performance inference of OpenAI’s Whisper automatic speech recognition (ASR) model”, is MIT-licensed, runs on everything from a Raspberry Pi to WebAssembly, and supports integer quantisation of the Whisper weights so a transcription model fits in very little memory. On a machine with no NVIDIA hardware it is often the path of least resistance, and its GGML model files work the same way GGUF files do in Part 6. Check the Whisper weights’ own licence on their model card before shipping anything.
Documents: try the text layer first
Section titled “Documents: try the text layer first”A PDF is either text with a layout or a picture of text, and the two need entirely different treatment. The mistake is to reach for the vision model first.
Born-digital PDFs carry their text. Extracting it is exact, instant and free of model error. PyMuPDF describes itself as “a high-performance Python library for data extraction, analysis, conversion & manipulation of PDF (and other) documents”, handling PDF, XPS, EPUB, MOBI, FB2, CBZ, SVG, TXT, MD and image formats, with page rendering, text extraction, table extraction, image extraction and OCR integration. Its companion project converts PDFs to Markdown, which is a convenient input for the chunking strategy in the previous lesson because it preserves headings.
Scans need pixels. Render each page to an image and send it to a vision model, or to a dedicated optical character recognition engine. This is one to two orders of magnitude more expensive per page than reading a text layer, so the sensible pipeline decides per page.
The rule that keeps this cheap:
- Extract the text layer for every page.
- Count pages that produced little or no text.
- Send only those pages through the vision path.
- Record which pages came from which path, because a reader of the answer deserves to know whether their citation came from a text layer or from a model reading a photograph.
That last point is not bureaucracy. Optical character recognition errors are not random noise; they are plausible substitutions, and a transposed digit in an invoice total is exactly the kind of error that survives every downstream check.
What runs on which track
Section titled “What runs on which track”Track S — NVIDIA DGX Spark
Vision and speech both work. llama.cpp with a projector is the simplest path and runs the same way as on any Linux machine. vLLM’s multimodal support is available here, and Part 9 covers getting vLLM running on this platform. NeMo follows the NVIDIA container path that Part 8 uses for aarch64, which is the smoothest route to Parakeet on this track. 128 GB of unified memory means the resolution and context limits above are advisory rather than binding.
Track X — AMD Ryzen AI Max+ 395Partial
llama.cpp with Vulkan or ROCm covers vision and whisper.cpp covers speech; NeMo and vLLM are the parts to check for your driver stack before relying on them.
llama.cpp’s multimodal path works through the same backend you already built in Part 6, so a vision model is a projector file away. For speech, whisper.cpp is the low-friction option on this track. Parakeet through NeMo depends on your ROCm PyTorch stack, which Part 8 covers with its status dated; treat it as something to verify rather than assume.
Track M — Apple silicon
Two good paths. llama.cpp with Metal handles vision models with a projector, and mlx-vlm is the MLX-native option with a command-line generator, a server and a chat interface. For speech, whisper.cpp has first-class Metal support and is the usual choice here. Unified memory means the image resolution you choose competes directly with everything else open on the machine.
Track N — NVIDIA desktop or laptop
Everything in this lesson runs, and video memory is the binding constraint rather than system memory. On an 8 GB card, keeping the projector off the graphics processor is the lever that makes a vision model fit; on 24 GB and above, the constraint moves to how many images you put in one context. NeMo and vLLM are both at home on this track.
Preserve evidence through modality conversion
Section titled “Preserve evidence through modality conversion”A scanned document passes through several transformations before a final answer: page rendering, OCR or vision encoding, extraction and possibly retrieval. Keep page numbers and source identifiers through the pipeline. Otherwise a plausible answer cannot be traced back to the region or timestamp that supports it.
Construct a small evaluation set with a rotated page, a multi-column layout, a table, an illegible field and a clean control. For audio, include silence, overlapping speakers and a proper noun whose spelling matters. Score extraction separately from the final task. A model cannot reliably reason from a total whose digits were transcribed incorrectly.
Define what happens when evidence is unclear: return an uncertainty marker or request review rather than converting unreadable text into a confident value. Record preprocessing resolution, audio segmentation and model identity alongside the answer. These are part of the experiment, since changing them changes both information available to the model and resource use. Text-only token counts do not fully describe the multimodal request’s memory and compute requirements.
Multimodal input is an encoder and a projector that turn pixels or audio into tokens in the
ordinary context, which is why images cost context length, key-value cache and memory in
exactly the ways Parts 4 and 6 taught. llama.cpp reaches vision models through libmtmd and
a separate projector file, with llama-server exposing them on the OpenAI-compatible chat
API; vLLM takes multimodal content through multi_modal_data offline and typed content items
online, with a per-prompt limit worth setting; mlx-vlm is the MLX path on a Mac. Qwen3-VL-8B
is the course’s reference vision model under Apache-2.0. Parakeet is the pinned speech model,
CC-BY-4.0 rather than Apache-2.0, run through NeMo; whisper.cpp is the portable alternative
and the easier one on tracks where the PyTorch stack is awkward. For documents, extract the
text layer first, count the pages that failed, and send only those through the expensive
path, recording which page came from which so that a citation means something.
Check your understanding
Sources for this lesson
9 verified · checked 2026-09-08
- 01llama.cpp — Multimodal support§ libmtmd; --mmproj and -hf; supported model familiesgithub.com/ggml-org/llama.cpp/blob/master/docs/multimodal.md2026-09-08
- 02Qwen3-VL-8B-Instruct model card§ Capabilities; context length; deploymenthuggingface.co/Qwen/Qwen3-VL-8B-Instruct2026-09-08
- 03vLLM — Multimodal Inputs§ Offline inference; online serving; --limit-mm-per-promptdocs.vllm.ai/en/latest/features/multimodal_inputs.html2026-09-08
- 04mlx-vlm§ README; installation; CLI and servergithub.com/Blaizzy/mlx-vlm2026-09-08
- 05Parakeet TDT 0.6B v3 model card§ Model overview; licence; input format; long-form audiohuggingface.co/nvidia/parakeet-tdt-0.6b-v32026-09-08
- 06NVIDIA NeMo — Automatic Speech Recognition§ Transcribing with a pretrained modeldocs.nvidia.com/nemo-framework/user-guide/latest/nemotoolkit/asr/intro.html2026-09-08
- 07whisper.cpp§ README; licence; supported platformsgithub.com/ggml-org/whisper.cpp2026-09-08
- 08ggml-org/Qwen2.5-VL-7B-Instruct-GGUF on Hugging Face§ Quantised files; usage with the llama.cpp serverhuggingface.co/ggml-org/Qwen2.5-VL-7B-Instruct-GGUF2026-09-08
- 09PyMuPDF — About§ Licensing; capabilitiespymupdf.readthedocs.io/en/latest/about.html2026-09-08
Every technical claim on this page was checked against the official documentation of the tool, vendor or model publisher on the date shown, at the version pinned for the course. Where the course disagrees with folklore, the source is how you can tell which one to trust.