Multimodal models: images, audio, video
SeniorHow non-text input becomes tokens, with worked patch and token arithmetic, projectors, fusion architectures, image/audio/video cost and KV-cache numbers, OCR vs VLM, and where multimodal grounding breaks.
Created 4 September 2026 · Updated 14 October 2026
8 concepts · detail hidden
01Glossary — terms used in this page
#c-glossary-terms-used-in-this-page- Modality — a kind of data: text, image, audio, video.
- Token — a unit the transformer reads; for text a word piece, for images a patch embedding, for audio a short time slice.
- Embedding — a vector (list of numbers) representing a token; the model works only on embeddings.
d_model— the width of the LLM’s embedding vectors, e.g. 4096.- Pixel / RGB channel — an image is a grid of pixels, each with 3 numbers (red, green, blue), usually 0 to 255.
- Patch — a small square block of pixels (e.g. 14x14) treated as one input unit.
- Patch size
P— the side length of a patch in pixels. - ViT (Vision Transformer) — a transformer that takes image patches as its input tokens.
- Vision encoder — the ViT (or similar) that turns an image into a set of vectors.
- CLIP — a model family trained to put an image and its caption close together in one vector space (contrastive learning).
- Contrastive learning — training so matching pairs (image, its caption) score higher than mismatched pairs.
- Projector / adapter — a small trained layer or MLP that converts vision vectors to the LLM’s embedding width.
- MLP (Multi-Layer Perceptron) — a small network of linear layers with non-linearities between them.
- VLM (Vision-Language Model) — an LLM that accepts images as well as text.
- VQA (Visual Question Answering) — training/evaluation data of images with questions and answers.
- Prefill — the phase where the model reads the whole prompt in parallel before generating; its cost grows with prompt length.
- KV cache — stored key/value vectors of all earlier tokens, so each new token need not recompute them; it grows with context length.
- Tiling — cutting a large image into several fixed-size crops that are each encoded separately.
- Resampler / Q-Former — a module with a fixed number of learned queries that compress many vision tokens into few via cross-attention.
- Cross-attention — attention where the queries come from one sequence (text) and keys/values from another (image features).
- Early fusion — mixing modalities in one model from the start of training, rather than attaching a vision module to a finished LLM.
- RoPE (Rotary Position Embedding) — a way of encoding token position by rotating query/key vectors; see Self-attention and positional structure.
- OCR (Optical Character Recognition) — software that extracts text characters from images.
- Mel spectrogram — a picture of audio: time on one axis, frequency bands (on a perceptual “mel” scale) on the other.
- Audio codec / tokenizer — a model that compresses audio into a stream of discrete codes and can reconstruct it.
- ASR (Automatic Speech Recognition) — converting speech to text (e.g. Whisper-style models).
- Grounding — keeping the model’s statements tied to what is actually in the input.
02How an image enters an LLM's context — the encoder-to-tokens pipeline
#c-how-an-image-enters-an-llms-context-the-encoder-to-tokens-piA transformer only consumes a sequence of vectors. Text gets there through a token-id lookup table. An image has no ids, so it is converted into vectors in four stages.
image 224x224x3 | (1) cut into 14x14 patches -> 16 x 16 = 256 patches v [ patch 1 ][ patch 2 ] ... [ patch 256 ] each patch = 14*14*3 = 588 raw numbers | (2) vision encoder (ViT): linear patch embedding + transformer layers v 256 vectors, each 1024-d (the encoder's width) | (3) projector: map 1024 -> 4096 (the LLM's d_model) v 256 "image tokens", each 4096-d -- look like text-token embeddings | (4) splice into the text sequence where the image occurred v [ "Describe" ][ "this" ][ img_1 ... img_256 ][ "image" ][ "." ] -> ordinary LLM stackStep 1 and 2 in code: patchify, then embed
import numpy as np H = W = 224; P = 14 img = np.arange(H * W * 3, dtype=np.float32).reshape(H, W, 3) # stand-in for real pixels # split into non-overlapping PxP patches and flatten each one patches = img.reshape(H // P, P, W // P, P, 3).transpose(0, 2, 1, 3, 4).reshape(-1, P * P * 3) print(patches.shape) # -> (256, 588) 256 patches, 14*14*3 = 588 numbers each rng = np.random.default_rng(0) W_patch = rng.standard_normal((P * P * 3, 1024)).astype(np.float32) * 0.01 # learned in a real model emb = patches @ W_patch print(emb.shape) # -> (256, 1024) one 1024-d vector per patch W_proj = rng.standard_normal((1024, 4096)).astype(np.float32) * 0.01 # the projector (learned) tok = emb @ W_proj print(tok.shape) # -> (256, 4096) now the same width as text-token embeddingsWalkthrough: the reshape cuts the 224 rows into 16 groups of 14 and the 224 columns into 16 groups of 14, the transpose brings each 14x14x3 block together, and the final reshape flattens each block to 588 numbers. The patch-embedding matrix (588 x 1024, about 603 thousand parameters including bias) is what turns raw pixels into the encoder’s vectors; in a real ViT transformer layers then mix information between patches. The projector multiplies by a 1024 x 4096 matrix so the result can sit in the LLM’s embedding space. Weights here are random stand-ins; in a real model they are learned.
What is trained and what is frozen
The vision encoder is often pretrained separately with a contrastive image-text objective (CLIP-style) and kept frozen or lightly tuned. Most multimodal capability comes from training the projector and the LLM on interleaved image-text data (captions, VQA, instructions), so the LLM learns to attend across the image tokens.
Why a projector is needed at all
The encoder’s vectors live in a space with its own coordinates; the LLM’s embedding table lives in another. Without a learned map between them, image vectors would be meaningless noise to the LLM. A linear projector (1024 x 4096, bias included, about 4.2 million parameters) or a two-layer MLP (about 21 million) is enough because the encoder already encodes semantics; the projector only needs to translate coordinates.
03Image token cost — how resolution and tiling drive it
#c-image-token-cost-how-resolution-and-tiling-drive-itAn image is not one token; it becomes
(width / P) * (height / P)tokens, rounded as the encoder dictates.The arithmetic
tokens = (W / P) * (H / P) W, H = image width/height in pixels (after resizing) P = patch side in pixels (fixed by the encoder)Image (after resize) Patch PGrid Tokens 224 x 224 14 16 x 16 256 224 x 224 16 14 x 14 196 336 x 336 14 24 x 24 576 448 x 448 14 32 x 32 1,024 1024 x 768 16 64 x 48 3,072 Doubling the side length (224 to 448) multiplies tokens by 4, because they scale with pixel area.
Token merging
Many models shrink the count after encoding by merging neighbouring patches: for example merging each 2x2 block of the 24x24 = 576 grid into one token gives 144 tokens (the merged token carries the four patches’ information in a wider vector or through a small MLP). Others use a resampler to produce a fixed number of tokens regardless of image size (see below).
Tiling for large images
Many systems split a big image into fixed-size tiles plus a downsampled overview. With 576 tokens per tile (illustrative), an image cut into 5 tiles plus 1 overview costs
5 * 576 + 576 = 3,456tokens: six times a single tile. This preserves small text but multiplies cost.Money and memory, with illustrative numbers
Take a hypothetical 576-token image and a price of $3 per million input tokens (a made-up price for the arithmetic):
one image : 576 tokens * $3 / 1,000,000 = $0.0017 1,000 images = $1.73 a 20-page PDF rendered at 1,000 tokens/page = 20,000 tokens (a long text prompt)Memory: the KV cache stores a key and a value per layer per token. For an illustrative model with 32 layers, 8 KV heads, head dimension 128 and fp16 (2 bytes):
bytes per token = 2 (K and V) * 32 layers * 8 heads * 128 dim * 2 bytes = 131,072 B = 128 KiB 576 image tokens -> 72 MiB 1,024 tokens -> 128 MiB 3,456 tokens -> 432 MiBAnd prefill attention is quadratic in total length: adding a 576-token image to a 500-token text prompt turns
500^2 = 250,000score entries into1,076^2 = 1,157,776, about 4.6x the attention work. See VRAM and running LLMs locally for how this competes with weights for GPU memory.Practical advice
- Resize or crop to the resolution the task needs: a photo caption does not need 4K; reading a dense receipt or a chart legend might.
- For screenshots with small text, crop the relevant region rather than raising resolution everywhere.
- Count image tokens in budgets and rate limits exactly as you do text tokens.
- Sending a video as frames multiplies the cost: 60 s sampled at 1 frame per second with 256 tokens per
frame is
60 * 256 = 15,360tokens before any text.
04Vision-language fusion architectures — interleaved tokens, cross-attention adapters, early fusion
#c-vision-language-fusion-architectures-interleaved-tokens-crosThree ways to let the language model use visual information.
(a) Interleaved tokens (LLaVA-style) (b) Cross-attention adapter (Flamingo-style) (c) Early fusion / native text + image tokens in ONE sequence text tokens stay text-only one model trained on all [t t t I I I I t t] [t t t t t] <--cross-attn--> [vision features] modalities from scratch | self-attention ^ extra layers inserted between LLM layers [t I a t I a ...] v shared weights unchanged LLM- Interleaved token fusion. Project patches into the LLM’s embedding space and concatenate them with text tokens. The existing self-attention handles cross-modal interaction with no architectural change. Simple and reuses a pretrained LLM, but image tokens consume the context budget, KV cache and compute exactly like text.
- Cross-attention adapters. The LLM’s own sequence stays text-only. New cross-attention layers let text
tokens query a separate set of vision features, often compressed by a resampler: a fixed set of, say, 64
learned queries attends to all 576 patch vectors and returns just 64 vectors. Cost of that step is about
576 * 64 = 36,864attention scores per head, rather than the576 * 576 = 331,776that full self-attention among the patches would need, and the LLM’s context sees 64 tokens or none. Decouples vision cost from the context budget, at the price of more architecture and training complexity, and some lost fine detail. - Early fusion / native multimodal pretraining. Train one model on interleaved text, image and audio tokens from the start, potentially with a shared embedding space or tokenizer. Highest ceiling for deep cross-modal reasoning; most expensive to train and requires large multimodal data.
Interleaved Cross-attention Early fusion Reuses a pretrained text LLM Yes Yes No Image cost in LLM context Full token count Small or none Full (native tokens) Training cost Low Medium High Cross-modal depth Good Limited by adapter Highest Frontier models have moved toward native multimodal pretraining because adapter-style bolt-ons cap how deeply the model can reason jointly across modalities.
05VLM vs traditional OCR pipeline for document understanding
#c-vlm-vs-traditional-ocr-pipeline-for-document-understandingTraditional OCR (a text detector plus a recogniser) is purpose-built for extracting characters. It is faster, cheaper per page, deterministic and reliable on clean, well-scanned text at scale, and it does not need a full LLM forward pass per page.
VLM extraction wins when the task requires understanding layout and meaning jointly.
Task OCR VLM Transcribe a clean scanned contract page Best (cheap, exact) Works, costs more Fill a schema (vendor, date, total, line items) from a messy invoice Needs extra parsing code per layout Strong “What is the trend in this chart?” Reads axis labels only Can answer Handwriting, skewed scan, mixed languages Brittle More robust Exact digits in a financial table Reliable if the scan is clean Can output plausible but wrong digits A robust hybrid for numbers
def extract_total(image, vlm, ocr): v = vlm(image, "Return the invoice total as a number only.") # semantic read ocr_text = ocr(image) # literal characters candidates = {t.replace(",", "") for t in find_amounts(ocr_text)} return { "value": v, "verified": str(v) in candidates, # the number appears literally on the page } # verified == False -> send to human review instead of trusting the VLMWalkthrough: the VLM supplies the semantic judgment (“which number is the total”), while OCR provides literal characters. If the VLM’s number is not among the amounts actually printed on the page, it is likely a hallucinated digit and is routed to a person. Use the same pattern, a cheap independent check, for any high-stakes field.
06How audio becomes input to a multimodal model — cascade vs native tokens
#c-how-audio-becomes-input-to-a-multimodal-model-cascade-vs-natCascade (transcribe, then text)
A speech-recognition model (Whisper-style) converts audio to text; the LLM only sees text.
audio -> ASR -> "please cancel my order" -> LLM -> text reply -> (optional) TTS -> audioSimple, reuses text-only infrastructure. But it drops everything non-lexical: tone (“please” said sarcastically), emphasis, pauses, overlapping speakers, background sounds, and transcription errors propagate uncorrected into the LLM.
Native audio tokens
Audio is converted to a stream of vectors or discrete codes that go into the model beside text.
waveform (e.g. 16,000 samples/s) -> mel spectrogram (about 100 frames per second, one column of frequency energies each) -> audio encoder (strided convolutions + transformer; stride 2 halves the rate) -> about 50 audio tokens per secondA Whisper-style encoder, for example, processes 30-second windows: 3,000 spectrogram frames become 1,500 encoder positions. At about 50 tokens per second, one minute of audio is
60 * 50 = 3,000tokens and a 30-second clip is1,500, so audio costs context like images do. Codec-based approaches instead quantise audio into discrete codes (often several parallel streams per time step) so the model can also generate audio tokens that a codec decodes back to sound.Cascade Native Tone, emphasis, speaker overlap Lost Available to the model Real-time voice conversation Added latency across 3 stages Lower latency possible Debuggability Easy (inspect transcript) Harder Complexity Low High Rule of thumb: for transcription-style tasks (meeting notes, search) a cascade is usually enough and cheaper; for tone-aware or real-time voice assistants, native audio is the better fit.
07Why multimodal context complicates positional encoding and KV-cache management
#c-why-multimodal-context-complicates-positional-encoding-and-kPositions: text is 1D, images are 2D
RoPE and similar schemes assign each token one index along a line, so distance means “how many tokens apart”. Flatten a 16x16 patch grid row by row and patch
(row 0, col 5)gets index 5 while the patch directly below it,(row 1, col 5), gets index 21: spatial neighbours are 16 positions apart in the sequence, while the left neighbour is 1 apart. A 1D scheme therefore misrepresents vertical adjacency.grid indices (16 wide) flattened order 0 1 2 ... 15 0 1 2 ... 15 | 16 17 18 ... 31 | ... 16 17 18 ... 31 above/below are 16 apart, left/right are 1 apartSome architectures give patches a pair of coordinates
(row, col)and apply positional rotations separately per axis (2D-aware or factorised schemes); video adds a third axis for time.KV cache: bursty and repeated
- A single image drops hundreds to thousands of tokens into the cache at once (e.g. 72 MiB for 576 tokens in the illustrative model above), unlike text generation, which adds one token at a time.
- A user often asks several questions about the same attached image or document. If the serving layer is not prefix-cache aware for the image portion, each follow-up re-encodes the image and re-prefills its tokens. Cache the image tokens (or their KV) keyed by the image content and keep the image at a stable position at the start of the prompt so a prefix cache can match.
08VLM hallucination vs text-only hallucination
#c-vlm-hallucination-vs-text-only-hallucinationBoth are fluent output that is not supported by evidence, but the missing evidence is different.
- Text-only hallucination is typically about facts absent from training data or context: the model asserts something ungrounded in any source it has (an invented citation, a made-up API). The right information was never available to it.
- VLM hallucination is often about misreading what is in front of it: the information is in the input (the pixels), but the model reports something else. It is a grounding failure.
Where the evidence gets lost
photo 4000x3000, a serial number "8472" printed 20 px tall | (1) resize to the encoder's input, e.g. 336 wide scale 336/4000 = 0.084 -> digits ~1.7 px tall v 336x336 pixels | (2) cut into 14x14 patches (24 x 24 = 576 patches) one patch >> one digit v 576 patch vectors <- a digit is smaller than one patch; its shape is blurred into the patch vector | (3) projector + LLM v LLM reads 576 image tokens + your question - where the visual evidence is weak, the language prior ("serial numbers look like 8472... or 3472?") fills inArithmetic check: a 20-pixel digit scaled by
336/4000 = 0.084becomes about20 * 0.084 = 1.7pixels, much smaller than the 14-pixel patch, so the model effectively cannot see the digit. To keep that digit at 14 pixels (one patch tall) you need a scale of 0.7. Cropping a 400 x 300 region around the number and scaling it by 0.7 gives a 280 x 210 image, which is(280 / 14) * (210 / 14) = 20 * 15 = 300tokens, versus 30,000 tokens to encode the whole 2800 x 2100 photo at that scale. Crop-and-zoom is far cheaper than raising resolution everywhere.Typical failures, each with the mechanism
Failure Example Why it happens Object hallucination Describes “a fork next to the plate” when no fork is visible Forks commonly co-occur with plates in training captions; the language prior supplies the likely object Miscounting Says 7 apples in a photo with 9 Counting is not a native operation; many small or overlapping objects are blurred into patches Wrong spatial relation Swaps left and right, or says the cup is “on” the table when it is beside it 1D token order and patch merging weaken precise geometry (see Why multimodal context complicates positional encoding and KV-cache management) Small-text misread Reads 8as3in a low-resolution serial number, or misses a footnoteGlyphs are smaller than a patch Chart misreading Reports a bar’s value from its label colour rather than its height Values must be measured against axes; the model pattern-matches typical chart text instead Attribute leak “The red car” when the only car is blue and a red sign is nearby Features of neighbouring regions blend Question-led agreement “What colour is the dog?” answered “brown” when there is no dog Presupposition in the question is trusted over the image Mechanism
It is a grounding failure rather than a knowledge gap: the language model’s prior about what typically appears in such scenes overrides the visual evidence the encoder extracted. Reasons include coarse patches (small things fall below patch resolution), the language side being much stronger than the visual side (the LLM was trained on far more text than the VLM on image-text pairs), and image tokens being a minority of the context so the text of the question dominates attention.
Text vs vision hallucination, side by side
Text-only VLM Is the right info in the input? Usually no Often yes, but not resolved Typical symptom Invented fact or citation Wrong object, count, position, digit First fix Retrieval, “I don’t know” Crop, zoom, higher resolution, region-by-region description Independent check Source lookup Detector, OCR, second crop Detection Compare to a reference text Compare to the image or a second view Measuring it: a polling probe
A standard way to measure object hallucination is to ask yes/no questions about objects that are present and objects that are absent, then check how often the model says “yes” to absent ones (this idea is used by published benchmarks of this kind; build your own on your own images).
def object_probe(vlm, items): """items: (image, object_name, truly_present). Yes/no questions about present and absent objects.""" tp = fp = fn = tn = 0 for image, obj, present in items: said_yes = vlm(image, f"Is there a {obj} in the image? Answer yes or no.").strip().lower().startswith("yes") if present and said_yes: tp += 1 elif present: fn += 1 elif said_yes: fp += 1 else: tn += 1 n = tp + fp + fn + tn return {"accuracy": (tp + tn) / n, "recall": tp / (tp + fn), "false_yes_rate": fp / (fp + tn), "yes_rate": (tp + fp) / n} def stub(image, question): # a biased stand-in: says yes whenever a table-scene object is mentioned return "yes" if ("fork" in question or "plate" in question) else "no" items = ([("img", "plate", True)] * 5 + [("img", "fork", True)] * 3 + [("img", "fork", False)] * 6 + [("img", "knife", False)] * 2 + [("img", "cup", True)] * 2) print(object_probe(stub, items)) # -> {'accuracy': 0.556, 'recall': 0.8, 'false_yes_rate': 0.75, 'yes_rate': 0.778} (rounded)Walkthrough:
tpcounts correct “yes”,fnmissed objects,fphallucinated objects (the key quantity),tncorrect “no”.false_yes_rate = fp / (fp + tn)is the fraction of absent objects the model claimed to see: here 6 of 8, or 0.75. A highyes_ratewith decent recall means the model leans on co-occurrence instead of looking. Balance present and absent objects so a model that always answers “yes” cannot score well.A second test: remove the image
Ask the same question with a blank or unrelated image. If the answer is unchanged, the model was answering from text priors and the image did not matter for that question. Questions that pass this test (answers change when the image changes) are the ones that measure vision.
Mitigations
- Ask the model to describe specific regions before answering (“List every object on the table, then answer”).
- Use higher resolution or crop and zoom on the area of interest when fine detail matters (the arithmetic above).
- Ask for evidence (“Quote the text you read in the bottom-left corner”).
- Avoid leading questions: ask “What is on the table?” rather than “What colour is the fork?”
- For counting and verification, use an independent check (a detector, OCR as in the hybrid above, a second pass at a different crop) rather than trusting one answer.
- Evaluate on your own images with known answers, as in Evaluating LLMs: benchmarks, contamination, judges; a public VQA score says little about your document type.