Personalize this lesson
Adapt explanations and teaching visuals to your background and preferred voice.
One inspection photo can raise two very different questions. A technician uploads a rack picture and types "blocked vent on rack door." The archive should find similar inspections, but the same technician might then ask for the printed asset tag or for a box around the hazard. A plausible match isn't enough evidence to close a safety ticket, so the system needs a cheap way to find candidates and a deliberate way to inspect them.
Contrastive Language-Image Pre-training (CLIP) gives us the cheap first pass: it places images and captions in one embedding space, so photos can be indexed offline and searched with text.[1] The rest of this design follows one rack photo from that score to the evidence a reviewer can trust.
Product brief: data-center visual inspection
The service lets technicians upload rack photos, search past inspections in natural language, and route uncertain hazards to a reviewer. CLIP or Sigmoid Loss for Language-Image Pre-training (SigLIP) powers first-pass retrieval. Optical character recognition (OCR), grounding, or a generative vision-language model (VLM) handles questions that need label text, regions, or explanations. That split keeps a similarity score from pretending to be a complete inspection.
Requirements and API
POST /v1/inspection-imagesaccepts an immutable image reference, site, rack, capture time, and idempotency key. It returnsimage_id, ingestion state, and encoder version.POST /v1/inspection-searchacceptsquery, authorized site filters,limit, and an optional minimum reviewed score. It returns rankedimage_idvalues, similarity scores, thumbnails, and review state. Scores are ranking signals, not probabilities.POST /v1/inspection-analysisacceptsimage_id,question, and requested evidence type. It returnsanswer, OCR spans or grounded regions, model versions, andanswered,abstained, orreview.- Search targets p95 below 300ms. Analysis targets p95 below 3 seconds. Access filters apply before retrieval, and a hazard never auto-closes from an unreviewed model result.
Data flow and sizing
Ingestion has one job: turn an authorized image into a searchable, versioned record. It follows upload -> malware and format checks -> metadata authorization -> image encoder -> normalized embedding -> versioned vector index. A failed check stops before the image becomes searchable.
Search has a different job. It follows authorized query -> text encoder -> site-filtered approximate nearest neighbor (ANN) search -> optional reranker -> response, with access filters applied before candidates are returned. Analysis starts only after the selected image is authorized, then follows crop or tile policy -> OCR or VLM -> grounding check -> review policy. The two paths share the versioned index, but they shouldn't share GPUs: a burst of expensive explanations must not slow a cheap text query.

Start with the vector bytes. For 10 million images and 768-dimensional 16-bit floating-point (FP16) embeddings, raw storage is 10,000,000 x 768 x 2 = 15.36 GB. That number excludes index structures, metadata, replicas, and old encoder versions.
The selected hierarchical navigable small-world (HNSW) graph or inverted-file (IVF) partitions determine the working set. If measured graph data, IDs, and filters take 2 to 4 times raw vectors, one generation needs roughly 31 to 61 GB of RAM. Replicas and a dual-generation rollout multiply it again. Measure the ANN library before choosing a 40 to 80 GB host budget.
At 100 queries per second (QPS), give the text encoder and ANN query path their own pool. Graph probes and encoder work shouldn't compete with the separately sized generative-analysis pool.
Recovery, rollout, and evaluation
Make ingestion idempotent by content hash and source version. Failed images enter retry or dead-letter state without becoming searchable. An encoder upgrade writes a new index generation; reads stay on the old complete generation until backfill and validation finish, then an atomic alias switch makes rollback cheap.
Analysis needs its own safe failure. If OCR or grounding fails, return abstained or review rather than a caption presented as verified evidence. A fluent sentence is still a model output, not a measurement.
Roll out with a frozen, technician-reviewed set, shadow traffic, a site-limited canary, and staged index switching. Block expansion when asset retrieval recall@10, hazard false-negative rate, asset-tag OCR accuracy, grounding IoU, p95 latency, review rate, or cross-site access tests miss their route-specific bars. Monitor drift by site, camera, lighting, rack type, and prompt template instead of relying on one aggregate benchmark.
Many supervised computer vision systems expose a fixed label inventory: recognizing a new category generally requires labeled examples and another training step. CLIP showed that image-text matching at web scale can produce representations that transfer to new text-defined classes without training a new classifier head.[1] That made practical open-vocabulary retrieval possible and provided useful vision features for later generative systems.
That gives us a routing plan, not yet a trustworthy score. Follow the uploaded rack photo into the shared space first; then we can see which questions need a different model.
Score the rack photo with text first
Embeddings are the first inspection index
Suppose the search box contains "blocked vent on rack door." Each stored photo has already become a 768-number embedding, and the query becomes another vector of the same size. The index doesn't need to compare pixels with words at request time; it ranks stored image vectors against the query vector.
If the model has learned the right visual signal, matching image and text vectors point in roughly the same direction. Cosine similarity measures that alignment with a score between -1 and 1. Within one trained model, a higher score ranks a pair as more aligned than a lower-scoring candidate.
The number still isn't a calibrated probability, and it doesn't prove that the image contains every detail in the caption. CLIP's contract is narrower: rank matching image-text pairs above mismatches.
What does cosine similarity measure in CLIP-style systems?
Answer
It measures whether an image embedding and a text embedding point in a similar learned direction. Use it to rank candidate matches; don't treat it as a calibrated probability or proof that a caption is correct.
Why rack_hazard is a bad first index
A fixed label list can teach one class called rack_hazard, but that class says little about how blocked airflow looks across lighting, camera angles, cables, and rack labels. What happens when a technician adds a new phrase next week?
Learning from many image-text pairs gives the model a broader signal. It learns to align a visual concept with a natural-language description, rather than memorizing only the names in one classifier head.
The shared space lets the system score text prompts that weren't fixed training labels. Transfer still depends on whether pre-training learned the relevant evidence and whether the new domain resembles its data. Validate prompts on the team's racks, lighting, cabling, and review policy before turning a score into an operational decision.
Why is open-vocabulary classification different from fixed-label classification?
Answer
Fixed-label classifiers choose from labels baked into training. CLIP-style models can score new text-defined classes without training a new head; launching a new class still requires validation and a threshold or review policy.
Shared image-text space
The shared map is the handoff between two different inputs. One encoder starts with pixels and the other with words, but both are trained so a matched pair receives a higher similarity than a mismatch. The original model learned this from 400 million image-text pairs.[1]
That makes a prompt such as "blocked airflow behind rack panel" possible even when the phrase wasn't a training label. It remains a transfer hypothesis: test it on the racks that matter before trusting the rank.
What is the shared image-text space buying you?
Answer
It gives image and text encoders a common coordinate system for ranking pairs. That enables retrieval or text-defined classification without retraining a new label head for every candidate task, subject to domain validation.
Two encoders, one rack-photo score
Architecture[1]
What has to happen inside the model for that shared map to exist? CLIP jointly trains an image encoder and a text encoder, so a rack photo and "blocked vent on rack door" land in one coordinate system. The original paper used either a ResNet (Residual Network) or a ViT (Vision Transformer) for images and a Transformer for text, producing one representation for each caption.[1]
Before the figure, predict the matching point: do the two encoders exchange pixels and words layer by layer, or do they meet after encoding? They meet after encoding. A contrastive objective pulls matched representations together and pushes mismatches apart.

The two lanes stay separate until their outputs are normalized and compared in the shared map. That separation is what lets us encode the image once, store it, and answer many text queries later.
Why is CLIP called a dual-encoder model?
Answer
It has one encoder for images and one encoder for text. Both produce embeddings in the same dimension, and the model scores image-text pairs by similarity rather than by decoding text.
Training objective
Training turns the shared map into a ranking game. For every image, the model must pick its paired caption from the other captions in the batch. The reverse direction asks whether each caption can find its paired image.
Use a tiny batch so the game stays visible. Suppose you have 3 images and 3 captions:
| Image | Caption |
|---|---|
| A | "cable label torn near port" |
| B | "asset tag partly covered by tape" |
| C | "warning label visible on power unit" |
There are possible pairings. Only the 3 diagonal pairs (A+A, B+B, C+C) are correct. The other 6 are mismatches. Before reading the labels in the figure, predict what training should do: raise the diagonal scores and lower the six off-diagonal scores.

The real batch in the original paper contains 32,768 pairs.[1] Each image therefore sees thousands of candidate captions, which supplies many negatives for the ranking game. The loss is symmetric: image-to-text and text-to-image each contribute a cross-entropy term. It's often described as InfoNCE-style (Information Noise Contrastive Estimation).
CLIP trains this setup from scratch with a linear projection from each encoder into the shared space. It also learns a temperature, initialized to the equivalent of 0.07 and clipped so logits aren't scaled by more than 100.[1] The temperature controls how sharply the model separates close scores.
Let and be the L2-normalized image and text embeddings. CLIP computes logits
where is a learned logit-scale parameter. You'll also see this written as division by a temperature , where . The symmetric objective is:
The first equation asks: "Given image , what's the probability that caption is the right one?" It compares the matching score with every caption score in row . The text-to-image equation does the same for column .
The final loss averages both directions. A model can't improve image retrieval while quietly letting text retrieval collapse, because both views must agree on the same diagonal.
In a batch of 3 images and 3 captions, which pairs should the contrastive loss reward?
Answer
Only the 3 diagonal image-caption pairs are positives. The other 6 off-diagonal pairs are negatives. The loss raises diagonal similarity and lowers mismatched similarities in both image-to-text and text-to-image directions.
The next implementation keeps the same symmetric pattern with a fixed temperature. It leaves out distributed batching and the learned scale so the matrix and its output stay inspectable.
1import math
2
3def normalize(vector: list[float]) -> list[float]:
4 length = math.sqrt(sum(value * value for value in vector))
5 return [value / length for value in vector]
6
7def dot(left: list[float], right: list[float]) -> float:
8 return sum(a * b for a, b in zip(left, right, strict=True))
9
10def cross_entropy(logits: list[float], target: int) -> float:
11 max_logit = max(logits)
12 log_sum_exp = max_logit + math.log(sum(math.exp(x - max_logit) for x in logits))
13 return -logits[target] + log_sum_exp
14
15def clip_loss(
16 image_embeds: list[list[float]],
17 text_embeds: list[list[float]],
18 temperature: float = 0.2,
19) -> tuple[float, list[list[float]]]:
20 images = [normalize(vector) for vector in image_embeds]
21 texts = [normalize(vector) for vector in text_embeds]
22 logits = [
23 [dot(image, text) / temperature for text in texts]
24 for image in images
25 ]
26
27 loss_i2t = sum(cross_entropy(row, i) for i, row in enumerate(logits)) / len(logits)
28 columns = [list(column) for column in zip(*logits, strict=True)]
29 loss_t2i = sum(cross_entropy(column, i) for i, column in enumerate(columns)) / len(columns)
30 return (loss_i2t + loss_t2i) / 2, logits
31
32image_embeds = [
33 [0.95, 0.05, 0.00],
34 [0.05, 0.92, 0.03],
35 [0.00, 0.07, 0.94],
36]
37text_embeds = [
38 [0.91, 0.08, 0.01],
39 [0.06, 0.89, 0.04],
40 [0.02, 0.09, 0.90],
41]
42
43loss, logits = clip_loss(image_embeds, text_embeds)
44print("logit matrix:")
45for row in logits:
46 print([round(value, 2) for value in row])
47print(f"symmetric_loss: {loss:.3f}")1logit matrix:
2[5.0, 0.6, 0.14]
3[0.71, 5.0, 0.66]
4[0.09, 0.59, 5.0]
5symmetric_loss: 0.022Read the output as a matrix, not as three independent scores. The diagonal entries are all 5.0, while most off-diagonal entries are close to zero, so the paired rows and columns win. A random or poorly aligned batch would look flatter and produce a higher loss.
That matrix also explains the systems cost. Softmax-based cross-entropy needs the full similarity matrix. CLIP benefits from very large batch sizes (32,768 in the original paper[1]) because each example then sees more in-batch negatives, but large batches require distributed execution and cross-device synchronization.
Why does original CLIP benefit from very large batch sizes?
Answer
The contrastive loss uses in-batch negatives. Larger batches give each image many more wrong captions to push away, but they also require a large similarity matrix and cross-device synchronization.
An off-diagonal pair is treated as negative, but it isn't automatically a clean semantic negative. Two photographs of the same rack hazard can be useful matches even when they arrived with different captions. Before scaling the batch, audit for those collisions so the loss doesn't push related examples apart.
1from dataclasses import dataclass
2
3@dataclass(frozen=True)
4class Pair:
5 image_id: str
6 caption: str
7 concept: str
8
9pairs = [
10 Pair("rack-front", "blocked vent on rack door", "blocked-airflow"),
11 Pair("rack-side", "airflow blocked by loose cabling", "blocked-airflow"),
12 Pair("asset-tag", "barcode obscured by cable tie", "blocked-barcode"),
13]
14
15potential_false_negatives = [
16 (left.image_id, right.image_id, left.concept)
17 for index, left in enumerate(pairs)
18 for right in pairs[index + 1:]
19 if left.concept == right.concept
20]
21
22print("pairs_treated_as_negative_but_related:", potential_false_negatives)1pairs_treated_as_negative_but_related: [('rack-front', 'rack-side', 'blocked-airflow')]Training data
The original paper names its constructed corpus WebImageText (WIT). It contains 400 million (image, text) pairs collected from the internet, with search queries chosen to cover a broad vocabulary rather than a closed label set.[1]
Unlike ImageNet-style datasets built around manual class labels, CLIP learns from natural language attached to images on the web. That supervision is broader and noisier. A pair of photos showing the same blocked vent can still become in-batch negatives.
Zero-shot classification
Zero-shot classification reuses the same ranking interface. Instead of a final layer with fixed class weights, the model compares one image embedding with text descriptions of the candidate classes.
Suppose a data-center rack photo must land in one of three categories. Before looking at the result, predict what the highest similarity will tell you, and what it still won't tell you:
| Class | Prompt | Similarity |
|---|---|---|
| rack hazard | "a photo of a rack hazard" | 0.87 |
| safe rack | "a photo of a safe rack" | 0.12 |
| blocked asset tag | "a photo of a blocked asset tag" | 0.41 |
The highest score is the top candidate, not a verdict. A real decision path needs a score or margin policy learned from held-out rack photos; ambiguous images should abstain or route to review. You didn't retrain the model, but you still have to calibrate the decision rule.
The same mechanism produced a striking research result. The CLIP paper's best model, ViT-L/14@336px, reaches 76.2% zero-shot top-1 on ImageNet and matches the original ResNet-50 without using ImageNet's 1.28 million crowd-labeled training examples.[1] A data-center prompt such as "cable label partially obscured by a tie" can be scored the same way, but reliability on your racks still has to be measured.
What changes between training a new classifier head and doing CLIP zero-shot classification?
Answer
A classifier head learns fixed class weights from labeled examples. CLIP zero-shot classification embeds natural-language class prompts and compares them to the image embedding, so the class set can change at inference time.
The function below makes the decision path explicit: write candidate classes as natural-language prompts, encode image and text, normalize both, and score them with a scaled dot product. Its hand-written vectors keep the inference mechanics visible without hiding them inside a model library.
1import json
2import math
3
4def normalize(vector: list[float]) -> list[float]:
5 length = math.sqrt(sum(value * value for value in vector))
6 return [value / length for value in vector]
7
8def dot(left: list[float], right: list[float]) -> float:
9 return sum(a * b for a, b in zip(left, right, strict=True))
10
11def softmax(values: list[float]) -> list[float]:
12 max_value = max(values)
13 exp_values = [math.exp(value - max_value) for value in values]
14 total = sum(exp_values)
15 return [value / total for value in exp_values]
16
17def zero_shot_classify(
18 image_embed: list[float],
19 class_names: list[str],
20 text_embeds: dict[str, list[float]],
21 logit_scale: float = 12.0,
22) -> dict[str, object]:
23 image = normalize(image_embed)
24 prompts = [
25 f"a photo of {'an' if name[0] in 'aeiou' else 'a'} {name}"
26 for name in class_names
27 ]
28 logits = [
29 logit_scale * dot(image, normalize(text_embeds[prompt]))
30 for prompt in prompts
31 ]
32 probs = softmax(logits)
33 best_index = max(range(len(probs)), key=probs.__getitem__)
34 return {
35 "predicted_class": class_names[best_index],
36 "scores": {
37 class_name: round(probability, 3)
38 for class_name, probability in zip(class_names, probs, strict=True)
39 },
40 }
41
42text_embeds = {
43 "a photo of a rack hazard": [0.92, 0.20, 0.05],
44 "a photo of a safe rack": [0.18, 0.88, 0.08],
45 "a photo of a blocked asset tag": [0.48, 0.31, 0.81],
46}
47
48result = zero_shot_classify(
49 image_embed=[0.89, 0.24, 0.08],
50 class_names=["rack hazard", "safe rack", "blocked asset tag"],
51 text_embeds=text_embeds,
52)
53print(json.dumps(result, indent=2))1{
2 "predicted_class": "rack hazard",
3 "scores": {
4 "rack hazard": 0.988,
5 "safe rack": 0.001,
6 "blocked asset tag": 0.01
7 }
8}Prompt engineering matters
The words around a class change the text vector. For a rack photo, compare a bare rack hazard label with "a photo of a rack hazard." The latter matches the sentence-like text CLIP saw during pre-training; on ImageNet, that template alone added 1.3% top-1.[1]
One wording can still be brittle. CLIP can encode several templates, average their normalized text embeddings, and cache one class vector. The original paper ensembled 80 context prompts and gained another 3.5% over the single default template; prompt engineering plus ensembling improved ImageNet by almost 5%.[1]
The code makes that averaging visible with a hardcoded list of prompt vectors. No single phrasing gets to decide the class representation by itself.
Why does prompt ensembling improve zero-shot classification?
Answer
Different templates emphasize slightly different contexts. Averaging normalized embeddings across templates reduces sensitivity to one brittle phrase and creates a more stable class representation.
1import json
2import math
3
4def normalize(vector: list[float]) -> list[float]:
5 length = math.sqrt(sum(value * value for value in vector))
6 return [value / length for value in vector]
7
8def mean_vector(vectors: list[list[float]]) -> list[float]:
9 return [
10 sum(vector[i] for vector in vectors) / len(vectors)
11 for i in range(len(vectors[0]))
12 ]
13
14prompt_vectors = {
15 "a photo of a rack hazard": [0.92, 0.20, 0.05],
16 "a picture of a rack hazard": [0.88, 0.24, 0.07],
17 "an image showing rack hazard": [0.86, 0.28, 0.08],
18 "a rack hazard in the wild": [0.82, 0.31, 0.12],
19}
20
21ensemble = normalize(mean_vector([
22 normalize(vector)
23 for vector in prompt_vectors.values()
24]))
25
26print(json.dumps({
27 "class": "rack hazard",
28 "templates": len(prompt_vectors),
29 "ensemble_embedding": [round(value, 3) for value in ensemble],
30}, indent=2))1{
2 "class": "rack hazard",
3 "templates": 4,
4 "ensemble_embedding": [
5 0.955,
6 0.284,
7 0.088
8 ]
9}Prompt ensembling reduces wording sensitivity, but it doesn't create a deployment threshold. The next policy uses held-out data to reject a low score or a narrow top-two margin, then exposes that uncertainty to downstream review.
1def decide(scores: dict[str, float], min_score: float, min_margin: float) -> str:
2 ranked = sorted(scores.items(), key=lambda item: item[1], reverse=True)
3 (best_label, best_score), (_, second_score) = ranked[:2]
4 if best_score < min_score or best_score - second_score < min_margin:
5 return "review"
6 return best_label
7
8cases = {
9 "clear_hazard": {"rack_hazard": 0.87, "safe_rack": 0.12, "tag_blocked": 0.31},
10 "ambiguous_panel": {"rack_hazard": 0.56, "safe_rack": 0.53, "tag_blocked": 0.18},
11}
12
13for name, scores in cases.items():
14 print(name, decide(scores, min_score=0.60, min_margin=0.10))1clear_hazard rack_hazard
2ambiguous_panel reviewLimitations of CLIP
Here is the boundary to test before wiring the score into an action: can one pooled similarity tell you how many vents are blocked or where each one is? No. Standard CLIP inference exposes a pooled image representation optimized for image-text matching, not calibrated counts or boxes. For counting and grounded localization, validate a detector such as Grounding DINO[2] or a grounding-aware VLM instead of interpreting global similarity as a region prediction.
That missing output is the key boundary. Zero-shot scoring compares one pooled image representation with one text representation. It returns a match score, not region coordinates or count evidence, so a concept-level hit can still come from the wrong component for an action that needs spatial proof.
Why is CLIP bad at counting and exact localization?
Answer
Its standard zero-shot interface scores one pooled image representation against one text representation. That output isn't supervised to return region coordinates, counts, or relations that detection or grounding models preserve.
Ask CLIP to "describe this image" and it returns similarity scores, not a sentence. A dual encoder has no language-generation head. Use a generative VLM such as LLaVA (Large Language-and-Vision Assistant) for captioning, or keep CLIP on retrieval and add a separate LLM for generation.
The same pooled contract can confuse relationships. CLIP benchmarks show failures when concepts are rearranged, such as "a red toolbox beside a blue safety cone" versus "a blue toolbox beside a red safety cone." The objective asks whether a caption matches an image overall; it doesn't supervise exact object positions or relations. Use a grounding-aware model or add a specialized detection head such as Grounding DINO for spatial reasoning.
Counting needs its own evidence path. If a team maps the top similarity score to "three rack hazards" and a labeled audit finds five, the score has been asked to do count-supervised work it never outputs. Use region-based models or an explicit counting module instead.
Printed text is another boundary. CLIP learns useful OCR features on digitally rendered words, which are common in its pre-training data, but the paper reports highly variable transfer across domains and formats.[1] Zero-shot CLIP reaches 88% on MNIST and is beaten there by logistic regression on raw pixels; full-number SVHN is only 51%, with failures on repeated characters, blur, and low resolution.[1] A pooled similarity score isn't a document-OCR interface, so route rack labels through a specialized OCR stage.
Finally, web-crawled supervision carries social bias. CLIP can associate visual concepts with stereotypes.[3] Audit embeddings on representative data before deployment, and treat debiasing or domain-specific fine-tuning as separate interventions rather than assuming a prompt fixes the problem.
When is CLIP the wrong tool even if it recognizes the concept?
Answer
Use a different tool when you need generated captions, object counts, bounding boxes, exact OCR, spatial relations, or safety-critical decisions that require calibrated, audited behavior.
Vision encoder architectures
The pooled vector is the wrong contract for counting vents or drawing boxes. To understand what a generative VLM can preserve, look one step earlier: what does the vision encoder emit before pooling? It emits a sequence of patch tokens, and that length becomes a serving input even when CLIP later reduces it to one vector.
Vision Transformer (ViT)[4]
Many published VLMs use Vision Transformers (ViT), while earlier systems used Convolutional Neural Networks (CNNs) such as ResNet. ViT treats an image as a sequence of patches and runs a Transformer encoder over them, much like Large Language Models (LLMs) process text tokens. Each patch becomes an embedding, and the stack returns contextualized visual tokens. CLIP pools those tokens into one image vector; a generative VLM may project many of them into the language prefix.

Patch tokenization
Before you count, predict the shape: a 224×224 image with 16×16 patches has 14 patches along each side. That gives visual tokens, plus a special [CLS] token used by many implementations to aggregate the global image representation.
How many patch tokens does a 224x224 image produce with 16x16 ViT patches?
Answer
patches per side, so patch tokens, plus an optional [CLS] token depending on the implementation.
Patch size is a token-budget choice. The figure uses 16x16 patches, while the high-resolution calculation below uses 14x14 patches. Compute the budget from the actual tower instead of copying a memorized number.
1def patch_tokens(height: int, width: int, patch_size: int, crops: int = 1) -> int:
2 assert height % patch_size == 0 and width % patch_size == 0
3 return (height // patch_size) * (width // patch_size) * crops
4
5for label, size, crops in [("thumbnail", 224, 1), ("page_crop", 336, 1), ("tiled_page", 336, 4)]:
6 print(label, patch_tokens(size, size, patch_size=14, crops=crops))1thumbnail 256
2page_crop 576
3tiled_page 2304The arithmetic tells us how many tokens exist. Architecture determines how those tokens acquire context. The comparison below keeps that distinction visible before we choose a connector.
ViT vs. ResNet
| Feature | Vision Transformer (ViT) | ResNet (CNN) |
|---|---|---|
| Scaling | Scales well with sufficient data; attention cost grows with patch-token count. | Strong convolutional baseline with different compute and inductive-bias trade-offs. |
| Context Window | Global context from the first layer via self-attention across all patches. | Local context initially, building to global context only in deep layers. |
| Inductive Bias | Low (treats image as sequence of patches), requiring more data to learn structure. | High (locality and translation-equivariance biases), often requiring less data to train from scratch. |
| Architecture | Transformer-shaped token sequence is convenient for many connectors. | Also usable as a vision tower; connector still maps its features to the downstream task. |
SigLIP[5]
Standard CLIP pays for a softmax over image-text candidates in the effective batch. Original CLIP used a batch size of 32,768 in its distributed training run; smaller implementations can still train, but they change the negative-diversity and accuracy trade-off.[1]
SigLIP (Sigmoid Loss for Language-Image Pre-training) changes the question. Rather than asking "Which of these 32,000 captions is the best match?", it asks "Is this specific image and caption a good match, yes or no?" It replaces the global softmax competition with a pairwise sigmoid loss:
Here is the batch size, for matching pairs and otherwise. and are the L2-normalized image and text embeddings, while and are learned temperature and bias parameters. The symbol is the logistic sigmoid.
The paper parametrizes as , initializes to , and initializes to . Training therefore starts near the all-negative prior, which fits a batch where most pairs are mismatches.[5] Each pairing is evaluated as its own binary classification problem. The paper's Algorithm 1 expresses the same idea as logits = t * sim + b, followed by -mean(log_sigmoid(z * logits)).
Advantages over CLIP
- Decoupled computation: No global normalization is needed. Each pair is scored independently, allowing the similarity matrix to be computed in chunks across devices.[5]
- Efficiency: The sigmoid loss removes global softmax normalization. The SigLIP paper reports a large gap versus softmax when the batch is smaller than 16k, a smaller gap as the batch grows, and diminishing returns past a more reasonable 32k. Pushing SigLiT to a 1 million batch was possible; it didn't keep helping.[5]
- Memory and partitioning: The loss is easier to chunk across devices because it doesn't require a single global softmax over the full batch.
SigLIP's pairwise loss changes the computation contract: positive and negative pair terms can be evaluated without a batch-wide probability distribution. That makes partitioning easier, but it doesn't decide what accelerator, data volume, or accuracy a data-center matcher will need.
What does SigLIP change relative to CLIP's softmax loss?
Answer
It scores image-text pairs with independent sigmoid terms instead of one global softmax over the batch. That reduces global synchronization pressure and works better at smaller batches, though large batches can still help.
1import math
2
3def softplus(value: float) -> float:
4 return max(value, 0.0) + math.log1p(math.exp(-abs(value)))
5
6def pair_loss(score: float, is_match: bool) -> float:
7 label = 1.0 if is_match else -1.0
8 return softplus(-label * score)
9
10scores = [
11 ("same_asset", 3.2, True),
12 ("wrong_caption", -2.1, False),
13 ("hard_negative", 0.4, False),
14]
15
16for name, score, match in scores:
17 print(name, round(pair_loss(score, match), 3))1same_asset 0.04
2wrong_caption 0.116
3hard_negative 0.913Why SigLIP matters in newer stacks
Some published VLMs use SigLIP-family towers when moderate-batch training or partitioned pair scoring matters. SigLIP 2 adds caption-based pretraining, self-distillation, masked prediction, and native-aspect-ratio support; its paper reports better transfer than original SigLIP across model scales when used as a VLM vision backbone.[6] The foundation remains image-text alignment. The loss and training recipe changed, not the need to test the target inspection slices.
EVA-CLIP: scaling vision backbones with masked pretraining[7]
Training a large Vision Transformer directly from scratch with contrastive loss is expensive, and scaling can become unstable. EVA-CLIP initializes its vision tower from an EVA masked image modeling (MIM) model, then uses that representation to make contrastive scaling more stable and efficient.[7] The tower therefore starts with features learned by reconstructing masked visual features from a teacher, rather than learning every visual pattern from image-text pairs alone.
The hybrid recipe also changes the sample-efficiency result. EVA-02-CLIP reaches 80.4% top-1 accuracy on ImageNet-1K with a 430M-parameter ViT-L/14+ and 82.0% with a 5.0B-parameter ViT-E/14+ in the paper's setup.[7] Those numbers describe ImageNet evaluation, not a guarantee that a data-center tower will read small asset tags.
Why does EVA-CLIP initialize its vision tower with masked image modeling?
Answer
Training massive vision transformers purely with contrastive loss is unstable and sample-inefficient. Initializing from an EVA masked image modeling checkpoint provides strong representations early, accelerating contrastive convergence and enabling stable scaling to billions of parameters.
Modern VLMs: from alignment to generation
The similarity score has reached its boundary. CLIP and SigLIP can say whether an image and caption belong together, but a technician asking "what's covering the barcode?" needs generated text and perhaps a box.
That question introduces fusion: the point where visual evidence and text interact to produce an answer. Before choosing a connector, predict the serving trade-off. Passing every patch to the language model preserves detail but lengthens its input; compressing patches protects the input budget but may discard the barcode.
Open papers give us three connector patterns to compare:
- simple projectors that map vision features into the LLM space
- query-based compressors that reduce many visual patches into a fixed token budget
- resamplers and adapters that preserve more spatial detail for high-resolution inputs
These patterns are enough to reason about token flow without guessing at proprietary internals.

LLaVA: the straightforward projector path[8]
LLaVA is the clearest example of projecting visual features into a language model. Original LLaVA takes a pretrained CLIP ViT-L/14 encoder and Vicuna (an instruction-tuned LLaMA chat model), then learns a linear projection matrix that maps encoder grid features into the LLM's word-embedding dimension.[8] The projector changes representation space; it doesn't decide whether a barcode is present.
The original recipe separates two learning problems:[8]
- Feature alignment: freeze the vision encoder and LLM, train only on a filtered slice of Conceptual Captions 3M (595K image-text pairs after filtering).
- Visual instruction tuning: fine-tune the projector and LLM on 158K GPT-4-generated multimodal instructions.
LLaVA-1.5 keeps that two-stage shape and changes two serving-relevant pieces: a two-layer MLP connector replaces the linear layer, and CLIP-ViT-L-336px replaces the 224px input. That resolution yields a grid, or 576 visual prefix tokens per image.[9] The pattern stays simple: a pretrained vision encoder, an LLM, and a learned projector can support multimodal instruction following without a new vision backbone.
What is the core LLaVA architecture recipe?
Answer
Use a pretrained vision encoder, project its visual features into the LLM hidden dimension, concatenate projected visual tokens with text tokens, then train first for feature alignment and then for multimodal instruction following. Original LLaVA uses a linear projector; LLaVA-1.5 switches to an MLP and a 336px CLIP encoder.
Common multimodal training ladder
The LLaVA split points to a broader training ladder. A VLM doesn't learn raw alignment, evidence routing, and polite answers in one jump. Each stage solves a different mismatch between what the model sees and what the product asks it to do.
| Stage | Typical objective | What it teaches |
|---|---|---|
| Contrastive pretraining | image-text matching as in CLIP or SigLIP | shared semantic space for retrieval and zero-shot recognition |
| Connector or projector alignment | freeze big backbones, train connector | map vision features into language-model space cheaply |
| Multimodal instruction tuning | image + prompt -> assistant response | teach task behavior, dialogue format, OCR usage, and grounded answers |
| Preference or safety post-training | chosen vs rejected responses, critiques, or policy filters | reduce harmful or low-quality multimodal behavior |
CLIP and SigLIP mostly cover the first row.[1][5] LLaVA makes the second and third rows explicit with feature alignment followed by visual instruction tuning.[8] A chat-oriented system needs training beyond contrastive alignment because matching doesn't teach long-form answer behavior.
Use the observed failure to locate the missing stage:
- contrastive pretraining teaches matching
- instruction tuning teaches how to answer
- post-training teaches which answers are preferred or allowed
If an assistant sees the right evidence but uses the wrong response format, improve instruction tuning, not CLIP data. If it gives polished but unsafe grounded answers, improve post-training, not the projector. Those stages still don't choose how many visual tokens reach the language path. BLIP-2's Q-Former answers that separate serving question.
BLIP-2 and the Q-Former: compress before the LLM[10]
BLIP-2 (Bootstrapping Language-Image Pre-training, version 2) addresses the token bottleneck directly. Instead of forwarding every visual feature into the LLM, it introduces a Q-Former that learns a fixed set of query vectors. The queries pull task-relevant information from a frozen vision encoder.
The published setup uses 32 queries of dimension 768. That bottleneck is much smaller than frozen ViT-L/14 features (, including the class token), and the query count stays fixed when input resolution rises.[10] The paper reports that BLIP-2 outperforms Flamingo-80B by 8.7 points on zero-shot VQAv2 while using fewer trainable parameters.[10] That's a research comparison, not a serving SLA for rack photos.
The serving consequence is straightforward: tokens admitted to the language path affect prefill and cache cost. A smaller fixed budget wins only if it preserves task-critical evidence. If 32 queries drop characters on an asset tag, lower prefix cost is the wrong result.
Why does BLIP-2 put a Q-Former before the LLM?
Answer
The Q-Former uses learned queries to extract a bounded set of useful visual tokens from a frozen vision encoder. That keeps visual evidence useful without sending every patch token into the expensive language model.
Flamingo-style resampling: preserve more context without exploding token count[11]
Flamingo takes a different route to fusion. It first compresses variable-length visual features with a Perceiver Resampler, then lets language tokens attend to that compact bank through gated cross-attention blocks inserted into a frozen LM.[11]
The published resampler emits 64 visual tokens, independent of image resolution or video-frame count.[11] Flamingo's vision tower is a frozen contrastively pretrained NFNet-F6, not a CLIP ViT, so don't copy its token count onto a LLaVA-style CLIP encoder. The transferable idea is the memory boundary: keep a compact visual bank and let later text tokens revisit it without placing every patch in the causal prefix. A gate initialized at zero keeps the frozen LM's text behavior intact at the start of training.[11]
Predict the workload where this boundary helps most. A single low-resolution photo may not justify cross-attention machinery, but the trade-off changes when a prompt contains:
- multiple images appear in one prompt
- video or long visual sequences are involved
- the language model needs to revisit visual evidence later in the answer
Resamplers and cross-attention can preserve a revisitable visual bank, but they add more moving parts than the simple LLaVA prefix. That complexity belongs in the design only when multiple images, long visual sequences, or repeated visual access pay it back.
When is a Flamingo-style resampler more attractive than simple projection?
Answer
Use it when prompts include multiple images, video frames, or long visual sequences where the language model needs to revisit visual evidence without paying for every raw patch token in the main prefix.
Qwen-VL: grounding and text reading beyond caption matching[12]
Qwen-VL (Qwen Vision-Language) shows what changes when grounding and text reading are first-class tasks. Its paper describes a visual receptor, an input-output interface, and a multi-stage training pipeline for OCR-heavy and box-grounded work in addition to generic image understanding.[12]
Those tasks need spatial signal. A tiny asset tag or a box around a vent can disappear when a high-resolution image is reduced to one pooled representation.
High-resolution token math
Suppose the vision tower uses 14×14 patches. Before the model reads the question, one 336×336 crop becomes patch tokens. Four such crops for one document page become 2,304 visual tokens. Predict what happens to a projected-prefix path before reading the next paragraph: the evidence improves, but the language model receives a much longer sequence.
In a projected-prefix VLM, those admitted visual tokens cost twice. They lengthen prefill, and they enlarge the KV cache that must remain resident during decode. PagedAttention can pack and recycle that memory more efficiently, but it doesn't reduce the prefix token count.[13] Cross-attention designs keep a separate visual bank, so measure their memory and latency path separately.

Why can high-resolution document images hurt projected-prefix VLM serving twice?
Answer
They can create many prefix tokens, which makes prefill slower, and those same tokens enlarge the KV cache that must stay resident during decoding. Memory managers help pack the cache, but they don't erase token count. Cross-attention designs need separate measurement because they expose visual evidence through a different path.
In a projector path, the connector decides how many encoder features become autoregressive prefix tokens. Other architectures expose a separate cross-attention memory bank instead. The comparison below uses raw tiles and a fixed-size compression path to count tokens; it doesn't claim that compression preserves enough OCR evidence.
1def prefix_tokens(text_tokens: int, crops: int, patch_tokens_per_crop: int, compressed_tokens: int | None) -> int:
2 visual_tokens = crops * patch_tokens_per_crop
3 if compressed_tokens is not None:
4 visual_tokens = compressed_tokens
5 return text_tokens + visual_tokens
6
7raw_prefix = prefix_tokens(text_tokens=180, crops=4, patch_tokens_per_crop=576, compressed_tokens=None)
8compressed_prefix = prefix_tokens(text_tokens=180, crops=4, patch_tokens_per_crop=576, compressed_tokens=64)
9
10print("raw_prefix_tokens:", raw_prefix)
11print("compressed_prefix_tokens:", compressed_prefix)
12print("tokens_avoided_if_quality_holds:", raw_prefix - compressed_prefix)1raw_prefix_tokens: 2484
2compressed_prefix_tokens: 244
3tokens_avoided_if_quality_holds: 2240When detail is expendable, resize. When small objects or text matter, tile or crop the image and summarize its regions. A Q-Former, resampler, or other projector-side bottleneck can instead compress the visual stream before it reaches the LLM.
Whichever path you pick, measure the evidence path with the budget. Check resolution, tiling, OCR, connector compression, and the model's measured failure slices together. BLIP-2's Q-Former and Flamingo's resampler show ways to bound or mediate visual features before language reasoning, but neither removes the need to test the asset-tag slice.[10][11]
Adaptive pruning and merging under a latency budget
A fixed visual-token budget makes capacity predictable, but it spends the same allowance on a blank panel and a dense wiring diagram. An adaptive reducer estimates which tokens carry useful information for each image, then drops or merges the rest.
Pruning and merging are different operations:
- Pruning removes low-importance tokens. It's cheap, but discarded evidence can't be recovered.
- Merging combines redundant tokens into retained representatives. It can preserve more aggregate context, but similarity mistakes can blend distinct objects or text regions.
PruMerge is one researched example for projected-prefix VLMs.[14] It uses sparse attention between the vision encoder's class token and spatial tokens to select important tokens per image. It then clusters tokens by key similarity and folds related pruned tokens into retained representatives with weighted averaging. The paper evaluates it on LLaVA-1.5 and Video-LLaVA. That makes it a candidate reducer, not a guarantee for every encoder, OCR route, or domain.
The reducer still needs a hard serving contract. If a dense label panel generates more candidates than the route can afford, the cap must win:
The route should also reserve minimum coverage for task-critical evidence. A dashboard policy might keep tokens that overlap OCR boxes, small-object proposals, or selected crops before spending the remaining budget on saliency. A global spatial sample protects against an importance scorer that focuses on one object.
| Control | Why it matters |
|---|---|
| Hard maximum tokens | Protects p95 prefill, memory, and concurrency from dense outliers |
| Task-specific token floor | Prevents an easy latency win from erasing text, defects, or small objects |
| Spatial coverage | Keeps neglected regions represented when saliency is concentrated |
| Token-count buckets | Reduces padding waste when adaptive requests are batched together |
| Full-token shadow sample | Measures whether reduction changes answers, OCR, or grounding |
Placement determines which cost disappears. Reducing tokens after the vision encoder saves connector and language-model work, but it doesn't save the completed vision-encoder pass. A reducer inside the vision tower can save later encoder work, but it changes more of the model and needs separate evaluation.
Track the admitted-token distribution, reduction overhead, vision latency, prefill time, time to first token, peak memory, and quality by slice. For this inspection product, OCR exact match, small-hazard recall, and grounding IoU matter more than average VQA alone. If the hard cap would remove protected evidence, use a slower route or return review instead of silently forcing the image through the fast path.
Why does an adaptive token reducer still need a hard maximum and protected evidence floors?
Answer
Adaptive counts can spike on dense inputs, so a hard maximum protects the latency and memory contract. Protected floors keep the reducer from meeting that contract by deleting OCR regions, small objects, or other evidence the task requires.
Connector design controls a serving trade-off
The component between the vision encoder and the LLM answers two serving questions: what visual evidence reaches generation, and how many visual tokens the language path must handle. A connector can save context and still fail the barcode task if it compresses the wrong region.
Compare four connector contracts:
- Linear projection: original LLaVA's . Cheap and simple, but a weaker mapping than an MLP.[8]
- MLP projector: LLaVA-1.5's two-layer connector. Still simple, usually stronger than a single linear layer.[9]
- Q-Former / query transformer: compresses many visual features into a fixed learned token set.[10]
- Convolutional or locality-aware abstractors: preserve more spatial structure before handing features to the LLM.[15]
An overcompressed connector can remove OCR or grounding evidence. A large or expensive connector can increase latency or language-prefix cost. Measure both outcomes by task slice.
There isn't one best adapter. Choose against the task you have to protect:
- raw simplicity
- token efficiency
- spatial fidelity
- video or multi-image support
How do you choose between a linear projector, MLP, Q-Former, and locality-aware adapter?
Answer
Use linear or MLP projectors for simple, cheap alignment. Use Q-Former-style compression when token budget matters. Use locality-aware adapters when spatial detail, OCR, or grounding matters enough to spend more compute.
Designing around unpublished internals
Closed vendors may publish capability demos and benchmark numbers without enough architecture detail to justify an internals claim. Treat that missing detail as a boundary, not an invitation to guess.
Treat the model as an evaluated dependency. Measure supported input resolution, visual-token billing if exposed, latency by image count, OCR and grounding slices, refusal behavior, and output contract. Don't infer a projector type, token budget, or training curriculum unless the provider publishes it.
What is the safe way to discuss closed VLM architectures?
Answer
Describe observed API behavior and measured task performance. Don't claim projector types, token budgets, or training recipes unless the vendor published them.
Evaluation
The connector decision is only a hypothesis until evaluation shows which evidence survives. A VLM needs more than one benchmark because retrieval, captioning, OCR, grounding, and reasoning stress different abilities.
| Benchmark | Focus | Metric | What it tells you |
|---|---|---|---|
| ImageNet | Zero-shot classification | Top-1 Accuracy | Whether the model recognizes common visual categories |
| COCO (Common Objects in Context) Captions | Captioning | CIDEr, BLEU | Whether the generated text overlaps with human references |
| VQAv2 | Visual question answering | Accuracy | Whether the model can answer grounded questions about an image |
| TextVQA | OCR + reasoning | Accuracy | Whether the model can read and reason over embedded text |
| MMMU | Expert multimodal reasoning | Accuracy | Whether it can combine specialist knowledge and visual evidence |
| MMBench | Fine-grained multimodal QA | CircularEval score | Whether performance holds across ability slices and reordered multiple-choice options |
Read the table as a set of failure probes, not a leaderboard. A CLIP-like model can retrieve well and still fail counting. Caption metrics can look fine while spatial reasoning is weak, and OCR-heavy tasks expose errors that general image description hides.
For the inspection product, keep these capability slices separate and set a pass bar for each one. A strong aggregate score can't rescue a route that loses asset-tag characters.
Why is one VLM benchmark insufficient for deployment?
Answer
Retrieval, captioning, VQA, OCR, expert reasoning, and prompt variation stress different capabilities. A model can look strong on one benchmark while failing counting, layout, text reading, or prompt-variation cases.
The small evaluation below makes that gate concrete. Before reading the output, predict the release decision from the three pairs of numbers: retrieval and grounding pass, but one route-specific slice doesn't.
1results = {
2 "asset_retrieval_recall_at_10": (0.94, 0.90),
3 "asset_tag_ocr_accuracy": (0.71, 0.92),
4 "rack_hazard_grounding_iou": (0.76, 0.70),
5}
6
7failed = [
8 metric
9 for metric, (measured, required) in results.items()
10 if measured < required
11]
12
13print("launch_ready:", not failed)
14print("failed_slices:", failed)1launch_ready: False
2failed_slices: ['asset_tag_ocr_accuracy']Production applications
Document understanding
Document AI is a token-budget problem. Dense documents contain tiny text, tables, figures, and layout cues that don't survive aggressive resizing.
Start with page tiling or crop-based preprocessing when small regions matter. Add OCR features for text that has to be copied exactly, then send ambiguous or layout-heavy cases to a VLM. The stages preserve different kinds of evidence.
The design target isn't maximum raw resolution. Preserve the evidence that matters while keeping the visual token budget survivable.
What is the core design goal for document understanding with VLMs?
Answer
Preserve the evidence that matters, such as small text, tables, layout, and figures, while keeping visual tokens and prefill cost survivable. Maximum raw resolution isn't the goal by itself.
Image search and retrieval
For image search, start with a dual encoder rather than a generative VLM. The search contract is ranking, not explaining.
A dual-encoder model like CLIP or SigLIP lets you precompute image embeddings offline, index them once, and embed only the query at request time.
This split keeps first-stage latency and cost measurable. A generative VLM earns a place after retrieval only when reranking or explanation needs its extra evidence contract and measured cost.
Why is CLIP or SigLIP usually better than a generative VLM for first-pass image search?
Answer
You can precompute image embeddings offline, index them once, and embed only the query at request time. A generative VLM is more expensive and better used for reranking or explanations after retrieval.
The running rack photo now forks by evidence type. Similarity search, OCR, generation, and grounding are different contracts, so don't send every photo through the generative VLM because one ticket asked for a caption.

The function turns that fork into an explicit route: retrieval stays on the dual encoder, label reading goes through OCR plus a generative VLM with a citation check, and an action such as clicking a disable port requires grounding plus human confirmation.
1def select_path(task: str) -> list[str]:
2 if task == "retrieve_similar_assets":
3 return ["dual_encoder", "vector_index"]
4 if task == "answer_from_label_text":
5 return ["ocr", "generative_vlm", "citation_check"]
6 if task == "click_disable_port":
7 return ["grounding_model", "action_policy", "human_confirm"]
8 raise ValueError(task)
9
10for task in ["retrieve_similar_assets", "answer_from_label_text", "click_disable_port"]:
11 print(task, "->", " + ".join(select_path(task)))1retrieve_similar_assets -> dual_encoder + vector_index
2answer_from_label_text -> ocr + generative_vlm + citation_check
3click_disable_port -> grounding_model + action_policy + human_confirmVisual agents
Visual agents need more than a captioning model. They need grounding, a link between an answer and the region or coordinate an action will use.
Suppose the agent must disable a port from a screenshot. "The blue button is near the top-right" isn't an executable target. The system may have to emit:
- click
(x, y) - select the second menu item
- type into the input field beneath a label
That usually requires a grounding-aware model or a separate grounding component on top of the core VLM, plus safety checks around actions. The same boundary applies to a rack hazard: a caption can explain, but policy needs a region and a human-confirmed action.
Why do visual agents need grounding beyond captioning?
Answer
Actions need coordinates, UI targets, object regions, or bounding boxes. A caption can say "the button is near the top-right," but an agent often needs a grounded action like click (x, y) with safety checks.
Common pitfalls
A useful diagnosis starts with the output contract that failed. Ask what the service returned, what evidence the task required, and where those two stopped matching.
"One pooled CLIP embedding understands an image like a person does"
If a team asks one global embedding for counts, boxes, or grounded actions, the output contract is already too broad. CLIP learned global alignment, not dense spatial supervision. Use detectors, grounding-aware VLMs, or region features when localization or counting matters.
"CLIP or SigLIP can also serve my chat and captioning path"
Similarity scores are the wrong output when the product needs an open-ended explanation or grounded dialogue. Keep CLIP or SigLIP for retrieval and zero-shot scoring, then use a generative VLM for the answer path.
"Higher resolution always means better OCR"
If document quality rises a little while latency and memory spike, resolution was increased without a token, tiling, or compression plan. Measure visual token count, tile selectively, preserve the evidence that matters, and compress before the LLM when possible.
"Visual serving cost ends when the encoder finishes"
Offline profiling can look cheap while production decode concurrency collapses. The missing terms are prefill latency and KV-cache growth from large visual prefixes, so track both with encoder cost before choosing image resolution or crop count.
"Adaptive pruning makes the latency budget self-enforcing"
Typical images can stay fast while dense screenshots create token spikes or erase tiny defects. That happens when the reducer has no hard maximum, protected evidence floor, or failure route. Cap the admitted sequence, preserve task-critical regions, bucket requests by retained count, and route unsafe reductions to a slower path or review.
"Every in-batch non-match is a clean negative"
Contrastive training can underperform on visually similar assets or captions that describe the same scene in different words. Some in-batch negatives are false negatives, so curate batches carefully, use richer captions, and treat batch design as a modeling choice.
"Zero-shot prompts will hold in fine-grained domains"
Prompt wording can swing predictions or collapse specialist categories when generic templates lack domain signal. Ensemble prompts, calibrate on validation data, and fine-tune or probe when domain precision matters.
What the inspection system must prove
Leave a reviewer with evidence for each route, not one attractive aggregate score. Your implementation is ready for review when you can:
- Explain how CLIP's dual-encoder architecture aligns images and text in a shared embedding space.
- Walk through the contrastive loss logic with a concrete batch example.
- Implement zero-shot classification using prompt engineering and embedding similarity.
- Diagnose why CLIP fails on spatial reasoning, counting, or fine-grained OCR.
- Compare LLaVA, BLIP-2, and Flamingo-style designs for connecting vision encoders to language models.
- Size the visual token budget for a production VLM and choose fixed or adaptive compression with explicit latency and evidence protections.