TAI BUI
Back to curriculum
BuildPython

Vision Transformers (ViT)

A ViT is a transformer encoder over image patches. An image becomes a sequence of patches the way a sentence becomes a sequence of tokens, and the same encoder reads both.

TIME: ~45 minutesPREREQUISITES: Phase 7 · 05 (Full Transformer), Phase 4 · 03 (CNNs), Phase 4 · 14 (Vision Transformers intro)

Learning Objectives

  • Split an image into flat patches and compute the patch count and flat patch size from the image and patch dimensions.
  • Explain how linear patch embedding, a `[CLS]` token, and positional embeddings turn patches into a transformer input sequence.
  • Estimate ViT parameter counts from Tiny to Huge and explain how patch size drives sequence length and attention cost.
  • Explain why ViT needs more data than a CNN, and what DeiT, Swin, DINOv2, and SigLIP each changed.

The Problem

A Vision Transformer (ViT) is a standard transformer encoder that reads an image as a sequence of fixed-size patches instead of a sequence of word tokens. Before 2020, computer vision meant convolutions. The state of the art on ImageNet, COCO, and detection benchmarks came from CNN backbones, and transformers were for language.

Dosovitskiy et al. (2020), "An Image is Worth 16x16 Words," showed you can drop the convolutions entirely. Slice an image into fixed-size patches, linearly project each patch into an embedding, and feed the sequence to a vanilla transformer encoder. At sufficient scale (ImageNet-21k pretraining or bigger), ViT matches or beats ResNet-based models.

ViT started a broader pattern: one architecture, many modalities. Whisper tokenizes audio. ViT tokenizes images. Robotics gets action tokens and video gets pixel tokens. The transformer doesn't care what the tokens are. Feed it a sequence and it learns.

By 2026, ViT and its descendants (DeiT, Swin, DINOv2, ViT-22B, SAM 3) own most of vision. CNNs still win on edge devices and latency-sensitive tasks. Outside those, most stacks have a ViT somewhere in them.

Warm-up0/2
1. A CNN applies the same small filter at every position of an image. What is that property usually called?Warm-up
A 7 by 7 grid representing an image with a 3 by 3 filter outlined at two positions, spot A top left and spot B lower right, and the 3 by 3 weight table used at each spot shown beside it.
Schematic. Weight values are illustrative.
2. A standard transformer encoder (Phase 7 · 05) reads its input as what?Warm-up
Four word pieces The, cat, sat, down feed into a box labelled Transformer encoder, next to a dashed box asking what the encoder reads as its input.
Typed example sentence.
Check: The Problem0/1
1. You must ship an image classifier on a latency-sensitive edge device. What does the lesson's view suggest?Design
A pipeline from a camera frame to an edge device with limited compute to a class label, a line marking a tight latency budget per image, and a dashed bar asking which model family the lesson suggests.
Schematic. No latency figure is given in the stem.

The Concept

Image → patches → tokens → transformer

Step 1: patchify

Patchify means splitting an H × W × C image into an N × (P·P·C) sequence of flat patches. The typical setup is a 224 × 224 image with 16 × 16 patches, which gives 196 patches of 768 values each.

image (224, 224, 3) → 14 × 14 grid of 16x16x3 patches → 196 vectors of length 768

Patch size is the main lever. Smaller patches mean more tokens, finer resolution, and quadratic attention cost. Larger patches are coarser and cheaper.

Step 2: linear embedding

A single learned matrix projects each flat patch to d_model. That is equivalent to a convolution with kernel size P and stride P. In PyTorch it is nn.Conv2d(C, d_model, kernel_size=P, stride=P), a two-line implementation.

Step 3: prepend [CLS] token, add positional embeddings

  • Prepend a learnable [CLS] token. Its final hidden state is the image representation used for classification.
  • Add learnable positional embeddings (original ViT) or 2D sinusoidal ones (later variants).
  • From 2024 on, RoPE extended to 2D handles position, sometimes without explicit embeddings.

Step 4: standard transformer encoder

Stack L blocks of LayerNorm → Self-Attention → + → LayerNorm → MLP → +. The block is BERT-style but pre-LN (LayerNorm comes before attention and the MLP, where BERT normalizes after), with no vision-specific layers. That is the pedagogical punchline of the paper.

Step 5: head

For classification, take the [CLS] hidden state, apply a linear layer, then softmax. DINOv2 and SAM discard [CLS] and use the patch embeddings directly.

Variants that mattered

ModelYearChange
ViT2020The original. Fixed patch size, full global attention.
DeiT2021Distillation; trainable on ImageNet-1k only.
Swin2021Hierarchical with shifted windows. Fixed sub-quadratic cost.
DINOv22023Self-supervised (no labels). Best general vision features.
ViT-22B202322B params; scaling laws apply.
SigLIP2023ViT + language pair, sigmoid contrastive loss.
SAM 32025Segment anything; ViT-Large + promptable mask decoder.

Why it took a while

ViT needs a lot of data to match CNNs because it lacks the CNN inductive biases (translation invariance, locality). Without more than 100M labeled images or strong self-supervised pretraining, CNNs still win at matched compute. DeiT narrowed the gap in 2021 with distillation tricks, and DINOv2 closed it in 2023 with self-supervision.

Check: The Concept0/24
1. What does the `[CLS]` token do in the original ViT classifier?Define
A pipeline from patch tokens p1 to pN, through a ViT encoder and a linear head with softmax, to class scores. Below it a dashed box shows a CLS token that is one learnable vector, with its role marked by a question mark.
Schematic.
2. How does SigLIP differ from the original ViT?Define
A table of ViT variants with only names and years: ViT 2020, DeiT 2021, Swin 2021, DINOv2 2023, ViT-22B 2023, SigLIP 2023, SAM 3 2025, with SigLIP highlighted, and a dashed box asking how SigLIP differs from the original ViT.
Names and years from the lesson's variants table.
3. What are register tokens?Define
A row of input tokens: patch tokens p1 to pN, then register tokens r1 to rk, with a colour legend for the two kinds.
Schematic. The number of register tokens k is left symbolic.
4. What is a patch in a Vision Transformer?Define
A schematic picture divided into a 4 by 4 grid, one cell outlined and marked 6 pixels by 6 pixels by 3 channels, with a dashed box asking what the transformer receives from that cell.
Schematic picture, not a real photo.
5. What does patchify do to an image?Define
A schematic picture labelled image (24, 24, 3) goes through patchify with P equal to 6, and a dashed box marks the output as unknown.
Schematic picture, not a real photo.
6. What is an inductive bias, as the lesson uses the term for CNNs versus ViT?Define
Two 5 by 5 grids. Left, a CNN layer with a 3 by 3 filter reads the 3 by 3 block around the orange cell. Right, a ViT attention layer reads every cell in the grid.
Schematic.
7. What is DINOv2?Define
A table of ViT variants with only names and years: ViT 2020, DeiT 2021, Swin 2021, DINOv2 2023, ViT-22B 2023, SigLIP 2023, SAM 3 2025, with DINOv2 highlighted and a dashed box asking what it is.
Names and years from the lesson's variants table.
8. What is Swin?Define
A table of ViT variants with only names and years: ViT 2020, DeiT 2021, Swin 2021, DINOv2 2023, ViT-22B 2023, SigLIP 2023, SAM 3 2025, with Swin highlighted and a dashed box asking what it is.
Names and years from the lesson's variants table.
9. Which standard operation is the ViT linear patch embedding equivalent to?Explain
A 224 by 224 by 3 picture with one patch outlined feeds a 16 by 16 by 3 block of 768 values, a learned matrix W of size 768 by d, and a token of width d_model, with a dashed box asking which standard layer does the same job.
Schematic picture, not a real photo.
10. Why does ViT need more training data than a CNN to reach similar accuracy?Explain
Two pipelines side by side. The CNN goes image, conv layers, pooling, head. The ViT goes image, patch embed, attention, head. A dashed box below says the ViT needs more training images and asks why.
Schematic.
11. Which development closed the remaining data gap by pretraining without labels?Explain
A timeline from 2020 to 2025 with ViT at 2020, DeiT and Swin at 2021, DINOv2 at 2023 and SAM 3 at 2025, and a dashed bar below asking which development closed the gap to CNNs on limited data.
Names and years from the lesson's variants table.
12. What do DINOv2 and SAM do with `[CLS]` that a classification ViT does not?Explain
A row of encoder outputs h0 to hN, where h0 is the slot of the CLS token, and two dashed boxes for the classification ViT head and for DINOv2 and SAM, each asking what is read or done.
Schematic.
13. Why is patch size described as the main lever in a ViT?Explain
A settings table for ViT-Base: image 224 by 224, patch size 16 by 16 highlighted and d_model 768, with a dashed bar asking what patch size sets.
Values from main.py's ViT-Base entry.
14. What problem does Swin's windowed attention solve compared with the original ViT?Explain
Two 8 by 8 grids of patches. In the original ViT grid, the orange patch reads from every patch. In the Swin grid, split into four 4 by 4 windows, the orange patch reads only from the patches inside its own window.
Schematic. The window size is illustrative; the lesson gives none.
15. What did DeiT change so that a ViT could be trained on ImageNet-1k alone?Explain
Two rows. The original ViT in 2020 matches or beats ResNets with ImageNet-21k pretraining or bigger. DeiT in 2021 is trainable on ImageNet-1k only. A dashed bar asks what DeiT changed.
From the lesson's Problem section and variants table.
16. How does the ViT encoder block differ from a BERT block?Explain
Two pipelines. BERT goes token and position embeddings, a block repeated L times, output. ViT goes patch embeddings plus CLS plus positions, a block repeated L times, head. A dashed bar asks what differs between the two blocks.
Schematic.
17. A 224 × 224 RGB image is cut into 16 × 16 patches. How many patches result, and how many values does each hold?Apply
A schematic 224 by 224 picture with a single 16 by 16 pixel patch outlined at the top left, and a note that the image has 3 colour channels.
Schematic picture, not a real photo. Only one patch is drawn.
18. You change a 224 × 224 setup from 16 × 16 patches to 8 × 8 patches. What happens to the token count and attention cost?Apply
Two copies of the same 224 by 224 schematic picture, the first with one 16 by 16 pixel patch outlined at the top left and the second with one 8 by 8 pixel patch outlined, both to scale.
Schematic picture, not a real photo.
19. A 32 × 32 RGB image is patchified with 8 × 8 patches. How many patches, and how many values per patch?Apply
A schematic 32 by 32 picture with a single 8 by 8 pixel patch outlined at the top left, and a note that the image has 3 colour channels.
Schematic picture, not a real photo; drawn at 7 screen pixels per image pixel.
20. A team has 30,000 labeled images, no pretraining budget, and wants to train a ViT from scratch. What is the likeliest outcome?Analyze
A three-row table: 30,000 labeled images, no pretraining budget, and a ViT trained from scratch, with a dashed bar asking for the likeliest outcome.
From the stem.
21. A team halves the patch size at 224 × 224 to catch small objects, and training memory and time jump sharply. What explains it?Analyze
Two copies of the same 224 by 224 schematic picture with one patch outlined: 16 by 16 before and 8 by 8 after, and a dashed bar noting that training memory and time jump sharply and asking why.
Schematic picture, not a real photo. One patch of each size shown, to scale.
22. DINOv2 features show a few patch tokens soaking up attention that does not belong to them. Which fix does the lesson point to?Analyze
A 10 by 10 grid of patch tokens in dark blue with three orange-red cells that receive far more attention than their neighbours, and a note asking which fix the lesson points to.
Schematic. Positions and the count of three are typed for illustration.
23. You must run a ViT on very high-resolution images and global attention is too costly. Which change from the lesson targets the cost directly?Analyze
A table at patch size 16: 224 pixels gives 196 tokens and 38,416 attention pairs, 448 gives 784 and 614,656, 896 gives 3,136 and 9,834,496, 1,792 gives 12,544 and 157,351,936, with a dashed bar asking which change targets the cost.
Example resolutions; counts computed.
24. You need one model that matches images to text descriptions for search. Which variant from the lesson fits?Design
A typed text query, red running shoes, a collection of four schematic photos, and a dashed box asking which model matches the text to the images.
Typed example query and schematic photos, not real data.

Build It

See code/main.py. It is a pure-stdlib patchify, linear embedding, and sanity-check script. There is no training, because ViT at realistic scale needs PyTorch and hours of GPU time.

Step 1: fake image

A 24 × 24 RGB image, stored as a list of rows of (R, G, B) tuples. With 6×6 patches that gives a 4 × 4 grid of 16 patches, each a flat vector of 108 values (6·6·3). The embedding width d_model is 48, set in main().

Step 2: patchify

The core of patchify is below (the real function also asserts that H and W divide evenly by P and returns (patches, (H // P, W // P)), so the caller gets the grid shape too).

python
def patchify(image, P):
    H = len(image)
    W = len(image[0])
    patches = []
    for i in range(0, H, P):
        for j in range(0, W, P):
            patch = []
            for di in range(P):
                for dj in range(P):
                    patch.extend(image[i + di][j + dj])
            patches.append(patch)
    return patches

Patches come out in raster order, row-major across the grid. ViTs follow this ordering.

Step 3: linear embed

Multiply each flat patch by a random (patch_flat_size, d_model) matrix. Then main.py prepends a [CLS] vector and adds 2D sinusoidal positions to the patch tokens. Here the [CLS] vector is a fixed seeded Gaussian draw (rng.gauss(0, 0.02)), not a trained parameter, and the positions are sinusoidal, not learned. The script prints a sequence length of 17 (16 patches plus [CLS]). It does not assert d_model, so check by hand that each token has 48 values.

Step 4: count parameters for a realistic ViT

main.py prints approximate parameter counts for five configurations: Tiny (~5.7M), Small (~22.0M), Base (~86.5M), Large (~304.1M), and Huge (~631.7M). ViT-Base is 12 layers, 12 heads, d=768, patch=16. For scale, ResNet-50 is about 25M, which the script does not print.

Check: Build It0/7
1. In `main.py`, `patchify` runs on a 24 × 24 image with 6 × 6 patches. Which order do the patches come out in?Apply
A schematic 24 by 24 picture cut into a 4 by 4 grid of 6 by 6 patches, beside an empty list of 16 slots numbered 0 to 15, with a note asking which grid cell fills each slot.
Schematic picture, not a real photo.
2. For the toy image in `main.py`, what are the flat patch size and the final sequence length?Apply
Six lines from main.py: H, W, C equal 24, 24, 3; patch_size 6; d_model 48; calls to patchify, linear_project and cls_and_pos. Below, a note asks for the flat patch size and the length of tokens_with_pos.
Lines copied from the lesson's main().
3. Roughly how large is ViT-Base, and how does it compare with ResNet-50 at about 25M?Apply
Two boxes: ViT-Base/16 with 12 layers, 12 heads and d equal to 768, and ResNet-50 with about 25M parameters; the ViT-Base parameter count is marked unknown.
ViT-Base settings from main.py; the ResNet-50 figure is from the stem.
4. In `patchify` with `P = 6` on the 24 × 24 image, the second patch appended to the list starts at which pixel?Apply
A 24 by 24 pixel grid split into 4 by 4 cells of 6 pixels with row and column pixel positions 0, 6, 12, 18, 24 marked on the axes and the top-left cell labelled 1st, plus a note asking where the second appended patch starts.
Schematic.
5. `patchify` runs on a 12 × 12 RGB image with `P = 4`. What does it return?Apply
Two code lines: make_image with 12, 12, 3 and patchify with P of 4, next to a schematic 12 pixel picture and a question about what is returned.
Schematic picture, not a real photo.
6. Using the parameter counts `main.py` prints, about how many times larger is ViT-Large (~304.1M) than ViT-Base (~86.5M)?Apply
Two boxes: ViT-Base/16 with L of 12, d of 768 and about 86.5M parameters, and ViT-Large/16 with L of 24, d of 1024 and about 304.1M parameters, with the ratio marked unknown.
Values from main.py's printed output.
7. You call `patchify` on a 25 × 25 image with `P = 6` and it fails an assertion. What is the cause?Analyze
Two lines of code: make_image with 25, 25, 3 and patchify with P of 6, followed by AssertionError in red, beside a schematic 25 pixel picture and a question about the cause.
Error name from a real run of the lesson's patchify. Schematic picture.

Use It

python
from transformers import ViTImageProcessor, ViTModel
import torch
from PIL import Image
 
processor = ViTImageProcessor.from_pretrained("google/vit-base-patch16-224-in21k")
model = ViTModel.from_pretrained("google/vit-base-patch16-224-in21k")
 
img = Image.open("cat.jpg")
inputs = processor(img, return_tensors="pt")
out = model(**inputs).last_hidden_state   # (1, 197, 768): [CLS] + 196 patches
cls_emb = out[:, 0]                       # image representation

DINOv2 embeddings are the 2026 default for image features. Freeze the backbone and train a tiny head. That works for classification, retrieval, detection, and captioning. Meta's DINOv2 checkpoints outperform CLIP on non-text vision tasks.

Patch-size picking. Small models use 16×16 (ViT-B/16). Dense prediction (segmentation) uses 8×8 or 14×14 (SAM, DINOv2). Very large models use 14×14.

Check: Use It0/3
1. Why does `last_hidden_state` for one 224 × 224 image from ViT-B/16 have shape (1, 197, 768)?Apply
Two lines of the Use It code, loading the ViT-Base patch 16 model and taking last_hidden_state, then the output shape drawn as three boxes holding 1, 197 and 768, with a note asking why 197.
Code lines from the lesson's Use It section.
2. You have 200,000 unlabeled product photos and need features for retrieval. Which approach does Use It suggest?Design
Six schematic photo tiles labelled 200,000 photos with no labels, an arrow to a note that image features are needed so similar photos can be retrieved, and a question about which approach to use.
Schematic tiles, not real photos.
3. You are building a segmentation model on a ViT backbone. Which patch size does Use It point to?Design
A schematic input picture goes through a ViT and comes out as a schematic label map with a class for every pixel, with a dashed bar asking which patch size the lesson points to for this task.
Schematic pictures, not real data.

Ship It

The skill in outputs/skill-vit-configurator.md picks a ViT variant and patch size for a new vision task, given dataset size, resolution, and compute budget.

Check: Ship It0/1
1. What does the Ship It skill `vit-configurator` produce?Define
A skill file named skill-vit-configurator with three inputs, dataset size, resolution and compute budget, feeding a dashed box that asks what the skill produces.
From the lesson's Ship It section.
Author's Note

I'd treat the encoder as the easy part and spend the effort on the front end. Step 4 says the block is BERT-style, and the closing line of main.py makes the same point: the vision-specific work lives in patchify, the positional scheme, and [CLS] pooling. If I were learning ViT from scratch, those are the three pieces I'd write by hand.

My bet is that patch size is the first number to check on a new setup. Attention cost is quadratic in tokens, and the token count falls straight out of the grid. The 24 × 24 toy image with 6×6 patches gives 16 patches and a sequence of 17 with [CLS]. A 224 × 224 image with 16×16 patches gives 196. I'd print that count before launching anything expensive.

I'd also read the positions in main.py as one choice, not the ViT default. The Concept names learnable embeddings as the original ViT approach, while main.py adds 2D sinusoidal ones and draws its [CLS] vector from a random Gaussian. Exercise 2 sets the two position schemes against each other on CIFAR-10, and I'd run it before assuming either one wins. The parameter counts in main.py are labeled approximate, so I'd use them for ballpark sizing and not as exact figures.

Exercises

  1. Easy. Run code/main.py. Verify that the number of patches equals (H/P) * (W/P) and that the flat patch dimension equals P*P*C.
  2. Medium. Implement 2D sinusoidal positional embeddings: two independent sinusoidal codes for the row and col of each patch, concatenated. Feed them into a tiny PyTorch ViT and compare accuracy against learnable positional embeddings on CIFAR-10.
  3. Hard. Build a 3-layer ViT in PyTorch and train it on 1,000 MNIST images with 4×4 patches. Measure test accuracy. Then add DINOv2-style pretraining on the same 1,000 images (simplified: train the encoder to predict patch embeddings from masked patches). Does accuracy improve?

Key Terms

TermWhat people sayWhat it actually means
Patch"The vision-transformer token"Flat vector of pixel values for a P × P × C region of the image.
Patchify"Chop + flatten"Slice image into non-overlapping patches, flatten each to a vector.
[CLS] token"The image summary"Prepended learnable token; its final embedding is the image representation.
Inductive bias"What the model assumes"ViT has fewer priors than CNNs; needs more data to make up the gap.
DINOv2"Self-supervised ViT"Trained without labels using image augmentation + momentum teacher. Best general image features in 2026.
SigLIP"CLIP's successor"ViT + text encoder trained with sigmoid contrastive loss; better than CLIP on matched compute.
Swin"Windowed ViT"Hierarchical ViT with local attention + shifted windows; sub-quadratic.
Register tokens"2023 trick"A few extra learnable tokens that soak up attention sinks; improves DINOv2 features.

Further Reading

Code

Ships With