Vision Transformers and the Patch-Token Primitive
Before anything multimodal, an image has to become a sequence of tokens a transformer can eat. The 2020 ViT paper did it with 16x16 pixel patches, a linear projection, and a position embedding. Five years later the frontier models (Claude Opus 4.7 at 2576px native, Gemini 3.1 Pro, Qwen3.5-Omni) still start there. The encoder moved from ViT to DINOv2 to SigLIP 2, register tokens showed up, and positions became 2D-RoPE, but the primitive held. This lesson walks the patch-token pipeline end to end and builds it in stdlib Python, so the rest of Phase 12 has a concrete picture of what "visual tokens" means.
Learning Objectives
- Convert an HxWx3 image into a sequence of patch tokens with correct positional encoding.
- Compute sequence length, parameter count, and FLOPs for a ViT of a given (patch size, resolution, hidden dim, depth).
- Name the three upgrades that took ViT from 2020 research to 2026 production: self-supervised pretraining (DINO / MAE), register tokens, and native-resolution packing.
- Pick between CLS pooling, mean pooling, and register tokens for a downstream task.
The Problem
Transformers operate on sequences of vectors. Text is already a sequence (bytes or tokens). An image is a 2D grid of pixels with three color channels, which is not. Flatten every pixel and a 224x224 RGB image becomes 150,528 tokens. Self-attention is quadratic in sequence length, so that is a non-starter.
Pre-2020 approaches bolted a CNN feature extractor onto the front. ResNet produces a 7x7 feature map of 2048-dim vectors, and you feed those 49 tokens to a transformer. This works, but it inherits the CNN's biases (translation equivariance, local receptive fields) and loses the transformer's appetite for scale.
Dosovitskiy et al. (2020) asked the blunt question: what if we skip the CNN? Split the image into fixed-size patches (say 16x16 pixels), project each patch linearly into a vector, add a positional embedding, and feed the sequence to a vanilla transformer. At the time this was heresy, vision without convolutions. With enough data (JFT-300M, then LAION) it beat ResNet on ImageNet and kept improving.
By 2026 the ViT primitive is the foundation most vision work builds on. The vision tower of an open-weights VLM is some descendant of it (DINOv2, SigLIP 2, CLIP, EVA, InternViT). Nobody asks "should we use patches?" anymore. The live questions are patch size, resolution schedule, pretraining objective, and positional encoding.
The Concept
Patches as tokens
Given an image x of shape (H, W, 3) and a patch size P, you carve the image into a grid of (H/P) x (W/P) non-overlapping patches. Each patch is a P x P x 3 cube of pixels. Flatten each cube to a 3 P^2 vector, then apply a shared linear projection W_E of shape (3 P^2, D) to map it into the model's hidden dimension D.
For the canonical ViT-B/16 config:
- Resolution 224, patch size 16 → grid 14x14 → 196 patch tokens.
- Each patch is
16 x 16 x 3 = 768pixel values, projected toD = 768. - Add a learnable
[CLS]token → sequence length 197.
The patch projection is mathematically identical to a 2D convolution with kernel size P, stride P, and D output channels. Production code implements it that way: nn.Conv2d(3, D, kernel_size=P, stride=P). Think of it as a linear projection, and write it as a convolution because that is the efficient form.
Positional embeddings
Patches have no inherent order, so the transformer sees them as a bag. Early ViTs added a learnable 1D positional embedding, one 768-dim vector per position, 197 of them. That works, but it ties the model to the training resolution: change the grid at inference and you have to interpolate the position table.
Modern vision backbones use 2D-RoPE (Qwen2-VL's M-RoPE, SigLIP 2's default) or factorized 2D positions. 2D-RoPE rotates the query and key vectors by the patch's (row, column) index, so the model reads relative 2D position off the rotation angle. There is no position table, and the model handles arbitrary grid sizes at inference.
CLS token, pooled output, and register tokens
What is the image-level representation? Three choices coexist:
[CLS]token. Prepend a learnable vector to the patch sequence. After all transformer blocks, the CLS token's hidden state is the image representation. Inherited from BERT, and used by the original ViT and CLIP.- Mean pool. Average the patch tokens' output hidden states. Used by SigLIP, DINOv2, and most modern VLMs.
- Register tokens. Darcet et al. (2023) observed that ViTs trained without an explicit sink token develop high-norm "artifact" patches that hijack self-attention. Adding 4-16 learnable register tokens absorbs that load and improves dense-prediction quality (segmentation, depth). DINOv2 and SigLIP 2 both ship with registers.
The choice matters downstream. CLS is fine for classification. A VLM that feeds patch tokens into an LLM skips pooling entirely, and each patch becomes an LLM input token. Registers get discarded before handoff because they are scaffolding, not content.
Pretraining: supervised, contrastive, masked, self-distilled
The 2020 ViT was pretrained with supervised classification on JFT-300M. Other objectives quickly took over:
- CLIP (2021): contrastive image-text on 400M pairs. Lesson 12.02.
- MAE (2021, He et al.): mask 75% of patches and reconstruct pixels. Self-supervised, works on pure images.
- DINO (2021) / DINOv2 (2023): student-teacher self-distillation with no labels and no captions. The 2023 DINOv2 ViT-g/14 is the strongest purely-visual backbone and the default for "dense features" use cases.
- SigLIP / SigLIP 2 (2023, 2025): CLIP with a sigmoid loss, plus NaFlex for native aspect ratio. The dominant vision tower in 2026 open VLMs (Qwen, Idefics2, LLaVA-OneVision).
The objective decides what the backbone is good for. CLIP and SigLIP match images to text, DINOv2 gives dense visual features, and MAE is a starting point for finetuning.
Scaling laws
ViT scaling (Zhai et al. 2022) established that quality follows predictable laws in model size, data size, and compute. At fixed compute:
- A bigger model with more data gives better quality.
- Patch size trades sequence length against fidelity. Patch 14 (typical for DINOv2 and SigLIP SO400m) gives more tokens per image than patch 16, which helps OCR and dense tasks and costs speed.
- Resolution is the other big lever. Going from 224 to 384 to 512 almost always helps, at quadratic cost in FLOPs.
ViT-g/14 (1B params, patch 14, resolution 224 → 256 tokens) and SigLIP SO400m/14 (400M params, patch 14) are the two workhorse encoders for 2026 open VLMs.
Parameter count for a ViT
The full calculation lives in code/main.py. For ViT-B/16 at 224:
patch_embed = 3 * 16 * 16 * 768 + 768 = 591k
cls + pos = 768 + 197 * 768 = 152k
block = 4 * 768^2 (QKVO) + 2 * 4 * 768^2 (MLP) + 2 * 2*768 (LN)
= 12 * 768^2 + 3k = 7.1M
12 blocks = 85M
final LN = 1.5k
total ≈ 86M
Ballpark a ViT this way before you load the checkpoint. The backbone size sets your VRAM floor in any downstream VLM.
2026 production config
Most open VLMs in 2026 ship SigLIP 2 SO400m/14 at native resolution (NaFlex). It has:
- 400M parameters.
- Patch size 14, default resolution 384 → 729 patch tokens per image.
- Mean pool for image-level tasks, with all 729 patches flowing into the LLM for VQA.
- 4 register tokens, discarded before LLM handoff.
- 2D-RoPE with image-level scaling for native aspect ratio.
Each choice in that config traces back to a paper you can read.
Use It
code/main.py is a patch tokenizer and geometry calculator. It takes (image H, W, patch P, hidden D, depth L) and reports:
- Grid shape and sequence length after patching.
- Token sequence for a synthetic 8x8 pixel toy image, walking through the flatten and project path.
- Parameter count broken down by patch embed, position embed, transformer blocks, and head.
- FLOPs per forward pass at the target resolution.
- A comparison table across ViT-B/16 @ 224, ViT-L/14 @ 336, DINOv2 ViT-g/14 @ 224, SigLIP SO400m/14 @ 384, and Qwen2.5-VL ViT @ 896.
Run it and match the parameter counts to the published numbers. Then change the patch size and resolution to feel what they do to the token count.
Ship It
This lesson produces outputs/skill-patch-geometry-reader.md. Give it a ViT config (patch size, resolution, hidden dim, depth) and it returns a token count, a parameter count, and a VRAM estimate with justifications. Reach for it when you pick a vision backbone for a VLM, since it catches the "tokens exploded and my LLM context filled up" surprise early.
I'd run main.py before reading the formulas. It starts with an 8x8 grayscale toy image, cuts it into four 4x4 patches, and projects each 16-pixel patch down to 4 numbers with a fixed fake matrix. That is the whole primitive in a few lines of list indexing, with no framework hiding the flatten step.
The part I'd poke at is seq_length. It adds one slot for a CLS token and one per register on top of the grid, and the position table is sized from that count, so the configs in the zoo show how those extra tokens cost real parameters. The zoo also marks SigLIP as cls_token=False with 4 registers, which is how the mean-pool-plus-registers setup from the Concept section looks in code.
One thing to watch: grid_shape raises if the image size isn't divisible by the patch size. That is why the SigLIP entry runs at 378 instead of 384, since 384 doesn't divide by 14. The prose rounds to 384, so trust the code when you check the numbers.
I'd also keep the calculator in proportion. It counts parameters and FLOPs from formulas, so it tells you the cost of a config and nothing about whether the encoder is any good. Pick the pretraining objective from the Concept section first, then use this to see what the token budget does to your LLM context.
Exercises
-
Compute the patch-token sequence length for Qwen2.5-VL at native 1280x720 input with patch size 14. How does that compare to a CLS-only representation?
-
A 1080p frame (1920x1080) at patch 14 produces how many tokens? At 30 FPS over a 5-minute video, how many total visual tokens? Which cost saves you most: pooling, frame sampling, or token merging?
-
Implement mean pooling over patch tokens in pure Python. Verify that mean-pool over 196 tokens of a DINOv2 output matches what the model's
forwardreturns when you ask for a pooled embedding. -
Read Section 3 of "Vision Transformers Need Registers" (arXiv:2309.16588). Describe in two sentences what artifact the registers absorb and why it matters for downstream dense prediction.
-
Modify
code/main.pyto support patch-n'-pack: given a list of images of different resolutions, produce a single packed sequence and the block-diagonal attention mask. Verify against Lesson 12.06 when you reach it.
Key Terms
| Term | What people say | What it actually means |
|---|---|---|
| Patch | "16x16 pixel square" | A fixed-size non-overlapping region of the input image; becomes one token |
| Patch embedding | "Linear projection" | A shared learned matrix (or Conv2d with stride=P) mapping flattened patch pixels to D-dim vectors |
| CLS token | "Class token" | Prepended learnable vector whose final hidden state represents the whole image; optional in 2026 |
| Register token | "Sink token" | Extra learnable tokens that absorb the high-norm attention artifacts ViTs develop during pretraining |
| Position embedding | "Positional info" | Per-position vector or rotation making the sequence-order-aware; 2D-RoPE is the modern default |
| Grid | "Patch grid" | The (H/P) x (W/P) 2D array of patches for a given resolution and patch size |
| NaFlex | "Native flexible resolution" | SigLIP 2 feature: single model serves multiple aspect ratios and resolutions without retraining |
| Backbone | "Vision tower" | The pretrained image encoder whose patch-token outputs feed the LLM in a VLM |
| Pooling | "Image-level summary" | Strategy to turn patch tokens into one vector: CLS, mean, attention pool, or register-based |
| Patch 14 vs 16 | "Finer vs coarser grid" | Patch 14 produces more tokens per image, better fidelity for OCR, slower; patch 16 is the classic default |
Further Reading
- Dosovitskiy et al.: An Image is Worth 16x16 Words (arXiv:2010.11929): original ViT.
- He et al.: Masked Autoencoders Are Scalable Vision Learners (arXiv:2111.06377): MAE, self-supervised pretraining.
- Oquab et al.: DINOv2 (arXiv:2304.07193): self-distillation at scale, no labels.
- Darcet et al.: Vision Transformers Need Registers (arXiv:2309.16588): register tokens and artifact analysis.
- Tschannen et al.: SigLIP 2 (arXiv:2502.14786): the 2026 default vision tower.
- Zhai et al.: Scaling Vision Transformers (arXiv:2106.04560): empirical scaling laws.
Code
- main.py (python, 188 lines)