Skip to content

13. Vision Transformers

The previous chapter built an encoder for sequences of text tokens. In 2021 Dosovitskiy et al. asked a deliberately blunt question: what if you feed an image to that same encoder, unchanged?1 The answer — "An Image is Worth 16×16 Words" — was that with enough data a near-vanilla Transformer beats a CNN at image classification, with no convolutions anywhere.

The result mattered less as a classification score than as a demonstration: the architecture does not need to know it is looking at an image. That is why the ViT is the bridge chapter. Once you have it, the image encoders inside CLIP, inside every vision-language model, inside Stable Diffusion and DiT all stop being separate things to learn.

Slides

Click the slides and use ← → to move through them; F goes full screen and P opens the presenter view, with the notes. The three simulators and the three diagrams of this page run inside the slides. Open the slides in a new tab

The inductive-bias trade, priced honestly

Chapter 10 called a convolution a strong, cheap, fixed prior. A self-attention layer has none of it: every patch may attend to every other patch from layer one, and any notion that neighbouring pixels are related has to be learned from data. Follow one patch through both, layer by layer:

One patch of a 14 by 14 grid, in two models. Left, 3 by 3 convolutions on the grid: the receptive field grows by one patch on each side per layer and reaches every patch only at layer 7. Right, self-attention: every patch is reachable from layer 1. The mean attention distances measured in a pretrained ViT-Ti/16 are 73, 84 and 13 pixels in layer 1 and 145, 142 and 145 pixels in layer 12. CNN · 3 × 3 CONVOLUTIONS ON THE SAME GRID VIT · SELF-ATTENTION 1 2 3

That is a trade, not an improvement, and the original paper measured both sides of it:

Pretraining data Result
ImageNet-1k only (~1.3M images) ViT loses to a comparable ResNet
ImageNet-21k (~14M) Roughly level
JFT-300M (~300M) ViT wins, and transfers better

The same result model by model: five ViTs from the paper's appendix and the largest ResNet of BiT13, each pretrained on the three sets and fine-tuned on ImageNet.

ImageNet top-1 accuracy after fine-tuning against the size of the pretraining set: ImageNet-1k, ImageNet-21k and JFT-300M. ResNet BiT R152x4: 81.3, 85.4, 87.5. ViT-B/32: 73.4, 81.3, 80.7. ViT-B/16: 77.9, 84.0, 84.2. ViT-L/32: 71.2, 81.0, 84.4. ViT-L/16: 76.5, 85.2, 87.1. ViT-H/14: 85.1 and 88.0 with the two larger sets.

Read the table as a single sentence: the convolutional prior is a floor at small scale and a ceiling at large scale. Below some data threshold it gives you for free what the Transformer has to buy; above it, it forbids relationships the Transformer is free to discover.

This threshold is not a law of nature, and it moved

DeiT2 showed that most of the "ViT needs 300M images" claim was really "ViT needs a training recipe nobody had written yet" — with the right augmentation, regularization and a distillation token, a ViT trains competitively on ImageNet-1k alone; Steiner et al.5 measured that the right augmentation and regularization, with more training compute, can be worth about as much as ten times more pretraining data. ConvNeXt8 then made the symmetric point from the other side. The modern reading is that recipe and scale explain most of the gap the 2021 papers attributed to architecture.

The pipeline

The only genuinely new mechanism is the first step. Everything after it is the encoder you already know.

An image \(x \in \mathbb{R}^{H \times W \times C}\) (\(H \times W\) pixels, \(C\) channels: 3 for RGB) is cut into non-overlapping \(P \times P\) patches. Each is flattened into \(P^2 C\) numbers and projected by one shared linear layer \(E\) to \(d\) numbers, the width of the model:

\[ x \in \mathbb{R}^{H \times W \times C} \;\xrightarrow{\text{patchify}}\; x_p \in \mathbb{R}^{N \times (P^2 C)} \;\xrightarrow{\;E\;}\; z \in \mathbb{R}^{N \times d}, \qquad N = \frac{HW}{P^2} \]

For \(224 \times 224\) and \(P = 16\), that is \(N = 196\) tokens. The 2021 ViT puts one more token in front, [CLS]: a learned vector, the same for every image, whose row at the output is the one the classification head reads. Positional embeddings are added, because attention alone cannot tell the top-left patch from the bottom-right one, and the sequence goes through \(L\) standard pre-norm encoder blocks4. Without positions the encoder is permutation-equivariant: shuffle the input tokens and the output tokens come out shuffled the same way, so neither a mean over them nor the [CLS] row can see the order (the jigsaw of the panel below).

Follow the tensor: ViT-B/16 at 224 px

The name reads model size / patch size: ViT-B/16 is the Base model (\(d = 768\), \(L = 12\), 12 heads) with \(P = 16\). One image, shape by shape:

Step Shape Parameters
Input image \(3 \times 224 \times 224\) —
Cut into \(16 \times 16\) patches, flatten \(196 \times 768\) — each row is \(16 \cdot 16 \cdot 3\) pixels —
Linear projection \(E\) (Conv2d(3, 768, 16, stride=16)) \(196 \times 768\) \(768 \cdot 768 + 768 = 590{,}592\)
Prepend [CLS], add positions \(197 \times 768\) \(768 + 197 \cdot 768 = 152{,}064\)
12 encoder blocks (attention + MLP \(768 \to 3072 \to 768\)) \(197 \times 768\) \(12 \times 7{,}087{,}872 \approx 85.1\text{M}\)
Final LayerNorm, read the [CLS] row \(768\) \(1{,}536\)
Linear head \(1000\) \(769{,}000\)
Total ≈ 86.6M

Two things to notice. The sequence length never changes inside the encoder: every block maps \(197 \times 768\) to \(197 \times 768\). And almost all the parameters are in the blocks, which do not know the image size — only the position table does, which is why changing the resolution means interpolating it. Step through what that does to ViT-B/16 fine-tuned at 384 px, and how 2-D RoPE avoids it:

The learned position table of ViT-B/16 at a new resolution. Trained at 224 pixels it has one row for the CLS token and 14 by 14 rows for the patches. At 384 pixels the image has 24 by 24 patches. The 196 patch rows are put back on their 14 by 14 grid and interpolated to 24 by 24; each new position is a weighted mean of four old ones. 2-D RoPE has no table: it rotates queries and keys by the row and column of each patch. [CLS] [CLS] ?

Patch embedding is a convolution

A linear projection over non-overlapping \(P \times P\) patches is exactly Conv2d(C, d, kernel_size=P, stride=P), and that is how every implementation does it — one op that patchifies and projects at once. So the "no convolutions" claim has an asterisk: there is exactly one, at the stem. With the interpolation of the position table when the resolution changes, it is one of the only two places where the 2-D layout of the image is built into the model by hand.

The whole pipeline, as code, on numbers small enough to check by hand: a 4 × 4 one-channel image, \(P = 2\) (four patches of four numbers), \(d = 3\) and one attention step of [CLS]. The vertical bar scores 0.436 for vertical; the same patches as a jigsaw score 0.393, and with the positions off both score 0.414. The checkerboard shows what the \(4 \to 3\) projection throws away.

x · image 4 × 4, 1 channel (edit it)

Patch size is the whole design decision

\(P\) controls two things at once, in opposite directions, and there is nothing else in a ViT with that much leverage.

Everything inside a patch becomes one token before attention ever runs. Attention moves information between patches, never inside one: from the first layer on, the model works on a \(\frac{H}{P} \times \frac{W}{P}\) grid, and anything that only exists below that grid has to survive the projection \(E\). Whether it can depends on a ratio, \(P^2C\) numbers in and \(d\) out:

Model \(P^2 C \to d\) What \(E\) can keep
ViT-B/16 \(768 \to 768\) everything, in principle — \(E\) is square
ViT-Ti/16 \(768 \to 192\) at most a quarter of the dimensions
ViT-B/32 \(3072 \to 768\) at most a quarter

The panel draws each patch with its mean colour, the crudest summary a token could hold. Drag the patch size to 32 and watch the face shrink to a handful of tokens; the lab rebuilds an image from the patch embedding of a real pretrained ViT-Ti/16 to show what a \(768 \to 192\) projection actually keeps.

Pull it the other way and the count explodes: halving \(P\) quadruples \(N\). Doubling the resolution does the same. Per image, a ViT costs about

\[ \underbrace{12\,L\,N\,d^2}_{\text{projections and MLP}} \;+\; \underbrace{2\,L\,N^2 d}_{\text{attention}} \quad\text{multiply-adds,} \]

so quadrupling \(N\) quadruples the first term and multiplies the second by 16. At 224 px the first term dominates: for ViT-B/16 attention is 4% of the 17.4 G multiply-adds, and going to \(P = 8\) costs 4.5×, not 16×. The quadratic takes over only when \(N\) passes \(6d\) — 4,608 tokens for ViT-B — which is exactly the regime of high resolution: at 448 px with \(P = 8\), attention is already 40% of the bill, and the \(N \times N\) score matrix has to fit in memory too. That is why:

  • 14 and 16 are the near-universal patch sizes, and have been since 2020;
  • high-resolution vision-language models tile a large image into several 224- or 336-pixel crops rather than feeding one huge grid to the encoder;
  • NaViT-style patch-and-pack9 exists at all — pack variable-resolution images into one sequence, keep the native aspect ratio, and skip the tokens you do not need.

What changed since 2021

The 2021 ViT is still recognizable, but a model you would download today differs in four places, and all four are worth knowing because they show up in the vision encoder of every multimodal model.

  • The [CLS] token mostly went away


    Reading the class from one special token was a BERT habit. Many current encoders use global average pooling or a small attention-pooling head (SigLIP's: one learned query that attends over the patch tokens) instead — as good or better, and it removes an odd token that has to be trained to be a summary. Some keep it for compatibility (DINOv2 still has one), but nothing depends on it any more.

    And in a vision-language model the classification head is gone entirely: what gets passed to the language model is the patch tokens, projected into the LLM's embedding space.

  • Learned positions → 2-D RoPE


    Learned absolute position embeddings have to be interpolated whenever the resolution changes, and that interpolation costs accuracy. 2-D RoPE — the rotation from chapter 11, applied separately to the row and column axes — is relative by construction and has no table to interpolate, so a change of resolution needs no surgery. Qwen2-VL and DINOv3 use it.

  • Register tokens


    Large, well-trained ViTs put enormous attention on a few meaningless background patches — using them as scratch space, which wrecks the attention maps and hurts dense tasks (an answer per pixel, such as segmentation or depth). Adding a handful of unused register tokens10 gives the model somewhere to put that state. The artifacts vanish, the attention maps become interpretable again, and dense-prediction scores improve. A few lines of code for artifacts the authors found in supervised, text-supervised and self-supervised ViTs alike (DeiT-III, OpenCLIP, DINOv2).

  • Hierarchy, where it is needed


    A plain ViT holds one resolution throughout, which is wasteful for detection and segmentation. Swin3 reintroduced windowed attention and a pyramid; ViTDet showed a plain ViT can also be adapted with a simple feature pyramid on top. Both are alive; the plain backbone won for pretraining, the hierarchical one for dense downstream tasks.

What a ViT is actually for in 2026

Very little of the deployed use is "classify this image into 1000 classes". The ViT won by becoming the standard way to turn pixels into tokens for something else:

Use What the ViT provides
Vision-language models The image encoder. Patch tokens are projected and prepended to the text sequence — this is how open models such as LLaVA and Qwen2-VL see; the proprietary ones do not publish their encoders.
Contrastive image-text The image tower of CLIP and SigLIP (chapter 19), trained against a text tower — the same text tower that later conditions image generators such as Stable Diffusion.
Self-supervised features DINOv27 and DINOv311 produce general-purpose dense features without labels that beat supervised ones on segmentation, depth and correspondence — used frozen, as a feature extractor, across robotics and medical imaging.
Generative backbones DiT and MMDiT are ViTs operating on latent patches instead of pixel patches (chapter 22).
Masked pretraining MAE6 — mask 75% of patches, reconstruct them. Cheap (the encoder only sees the visible 25%) and a strong initializer.

The reason it won is not accuracy

A ViT and a modern ConvNet score about the same on ImageNet. The ViT is everywhere anyway, because it emits a sequence of tokens in the same format text uses. Concatenating patch tokens with word tokens and running one Transformer over both is trivial; doing the equivalent with a convolutional feature map is not. Multimodality is the argument, not classification.

CNN vs. ViT, updated

CNN Vision Transformer
Core operation Convolution (local, fixed) Self-attention (global, learned)
Inductive bias Strong Weak — bought with data
Receptive field Grows with depth Global at layer 1
Cost \(O(\text{pixels})\) \(O(N d^2)\), plus \(O(N^2 d)\) attention that dominates once \(N > 6d\)
Small data Wins Needs pretraining or a DeiT-style recipe
Web-scale data Keeps up, given the same recipe and compute12 Where almost all web-scale pretraining happens
Multimodal use Awkward Native — it already emits tokens
Still standard for Latent autoencoders, medical segmentation, on-device Everything upstream of an LLM

Implementation reference

import torch, torch.nn as nn

class PatchEmbed(nn.Module):
    """Image -> sequence of patch tokens. One Conv2d does patchify AND projection."""
    def __init__(self, patch=16, in_ch=3, dim=768):
        super().__init__()
        self.proj = nn.Conv2d(in_ch, dim, kernel_size=patch, stride=patch)

    def forward(self, x):                    # (B, C, H, W)
        x = self.proj(x)                     # (B, dim, H/p, W/p)
        return x.flatten(2).transpose(1, 2)  # (B, N, dim)
class ViT(nn.Module):
    """Registers instead of a CLS token, mean pooling instead of a CLS head."""
    def __init__(self, dim=768, depth=12, heads=12, n_classes=1000,
                 n_patches=196, n_registers=4):
        super().__init__()
        self.patch_embed = PatchEmbed(dim=dim)
        # random init: identical registers would get identical gradients and stay identical
        self.reg = nn.Parameter(torch.randn(1, n_registers, dim) * .02)
        self.pos = nn.Parameter(torch.randn(1, n_patches, dim) * .02)
        layer = nn.TransformerEncoderLayer(dim, heads, dim * 4, dropout=0.0, activation='gelu',
                                           norm_first=True, batch_first=True)
        self.encoder = nn.TransformerEncoder(layer, depth, enable_nested_tensor=False)
        self.norm = nn.LayerNorm(dim)
        self.head = nn.Linear(dim, n_classes)
        self.n_reg = n_registers

    def forward(self, x):
        x = self.patch_embed(x) + self.pos
        x = torch.cat([self.reg.expand(x.size(0), -1, -1), x], dim=1)
        x = self.encoder(x)[:, self.n_reg:]      # drop the registers; they were scratch space
        return self.head(self.norm(x).mean(dim=1))
import timm
# a supervised ViT, fine-tuned on your classes
model = timm.create_model('vit_base_patch16_224', pretrained=True, num_classes=10)

# DINOv2 features, frozen — usually the stronger option when labels are scarce
dino = torch.hub.load('facebookresearch/dinov2', 'dinov2_vitb14')
feats = dino.forward_features(img)['x_norm_patchtokens']   # (B, N, 768)

Lab: the weak prior, measured

Every claim on this page is measured in a Colab notebook on CIFAR-10 (60,000 colour images, 32 × 32, ten classes). It runs in the browser on a free T4 GPU — under fifteen minutes of compute; there is nothing to install. Nine sections, each ending in a checkpoint (the first eight with a check value):

  1. patchify written by hand and checked against F.unfold;
  2. the patch embedding as a strided convolution, and what a pretrained ViT-Ti/16's \(768 \to 192\) projection keeps of each patch;
  3. a jigsaw-shuffled image that a ViT without positions cannot tell from the original;
  4. the price of a token, from the formula and from the clock;
  5. a small CNN against a TinyViT on 1,000, 5,000 and 20,000 images, with the same budget;
  6. the same at 5,000 images without augmentation — the recipe, at small scale;
  7. a frozen pretrained ViT-Ti/16 with a logistic regression on top, against the same architecture with random weights;
  8. what the pretrained ViT learned instead of the prior: the 2-D grid in its position table and the attention distance of each head;
  9. a ViT of your own for 5,000 images, under a 600,000-parameter budget.

Open the lab in Colab — save a copy to your Drive (File → Save a copy in Drive) to run and edit it.

Key takeaways

  1. A ViT is the text encoder applied to image patches. The only new part is patchify — and that is one strided convolution.
  2. The convolutional prior is a floor at small scale and a ceiling at large scale. That is the whole trade.
  3. The 2021 data threshold was largely a recipe problem. DeiT and ConvNeXt closed the gap from both directions.
  4. Patch size is the design decision: a patch is one token before attention runs, so nothing below the patch grid is seen except through the projection, and \(N\) grows quadratically as \(P\) shrinks or resolution grows. 14–16 is where the compromise lands; the attention term dominates the cost only once \(N > 6d\).
  5. A current encoder uses pooling instead of [CLS], 2-D RoPE instead of learned positions, and register tokens to absorb the attention artifacts.
  6. ViTs won because they emit tokens, which is what a language model eats. The classification score was never the point.
  7. In production a ViT is usually the vision encoder of something else: a VLM, CLIP/SigLIP, DINOv2 features, or a diffusion backbone.
  8. At small scale the weak prior is a real price. In the lab, on 1,000 to 20,000 CIFAR-10 images, a small CNN beats a ViT of similar compute by 15 to 20 points; a frozen ViT pretrained on 14M images beats both with 1,000 labels and a linear probe (a logistic regression on its frozen features). The prior can be learned — from far more data.


  1. Dosovitskiy, A., et al. (2021). An Image is Worth 16×16 Words: Transformers for Image Recognition at Scale — ICLR. ↩

  2. Touvron, H., et al. (2021). Training data-efficient image transformers & distillation through attention (DeiT) — ICML. The recipe that removed the JFT-300M requirement. ↩

  3. Liu, Z., et al. (2021). Swin Transformer: Hierarchical Vision Transformer using Shifted Windows — ICCV. ↩

  4. Vaswani, A., et al. (2017). Attention Is All You Need — NeurIPS. (The encoder block ViT reuses, with each LayerNorm moved before its sublayer: pre-norm.) ↩

  5. Steiner, A., et al. (2021). How to train your ViT? Data, Augmentation, and Regularization in Vision Transformers — TMLR. ↩

  6. He, K., et al. (2022). Masked Autoencoders Are Scalable Vision Learners — CVPR. Mask 75%, encode only what is visible. ↩

  7. Oquab, M., et al. (2024). DINOv2: Learning Robust Visual Features without Supervision — TMLR. Self-supervised features good enough to use frozen. ↩

  8. Liu, Z., et al. (2022). A ConvNet for the 2020s (ConvNeXt) — CVPR. ↩

  9. Dehghani, M., et al. (2023). Patch n' Pack: NaViT, a Vision Transformer for any Aspect Ratio and Resolution — NeurIPS. ↩

  10. Darcet, T., Oquab, M., Mairal, J., & Bojanowski, P. (2024). Vision Transformers Need Registers — ICLR. The high-norm artifact tokens, diagnosed and fixed. ↩

  11. Siméoni, O., et al. (2025). DINOv3 — arXiv. DINOv2 scaled up, with 2-D RoPE and dense features that stay clean at high resolution. ↩

  12. Smith, S. L., Brock, A., Berrada, L., & De, S. (2023). ConvNets Match Vision Transformers at Scale — arXiv. NFNets pretrained on JFT-4B match ViTs of the same compute budget. ↩

  13. Kolesnikov, A., et al. (2020). Big Transfer (BiT): General Visual Representation Learning — ECCV. ResNets pretrained on ImageNet-1k, ImageNet-21k and JFT-300M: the CNN baseline of the ViT paper. ↩