Before a Vision Transformer can attend to anything, the image has to become a sequence of tokens. Given an image size, a patch size and an embedding dimension, report the patch grid, the number of visual tokens, the final sequence length, and the parameter count of the patch-projection layer.
A ViT cuts the image into a non-overlapping grid of patch_size x patch_size squares:
grid_h = image_h // patch_size
grid_w = image_w // patch_size
num_patches = grid_h * grid_w
Each patch is flattened to a vector of length patch_size * patch_size * in_channels and pushed through a single shared linear layer into embed_dim. That layer is the patch embedding, and it has
proj_params = patch_size * patch_size * in_channels * embed_dim + embed_dim
parameters (weights plus one bias per output channel). It does not depend on how many patches there are - the projection is applied identically to every patch.
Most ViTs then prepend a learned [CLS] token whose final hidden state is used as the pooled image representation, so the sequence the transformer actually sees is one longer than the patch count:
seq_len = num_patches + 1 (with CLS)
This is the number that matters for a VLM: it is exactly how many positions the image will occupy in the language model's context if the visual tokens are passed through unpooled. CLIP ViT-L/14 at 224x224 gives a 16x16 grid = 256 patches, 257 positions with the CLS token.
Implement:
def patch_embedding_stats(image_h, image_w, patch_size, embed_dim, use_cls_token=True, in_channels=3):
Return a list [grid_h, grid_w, num_patches, seq_len, proj_params] of plain Python integers.
A list of five integers, as described above.
print(patch_embedding_stats(224, 224, 14, 1024))
Output:
[16, 16, 256, 257, 603136]
print(patch_embedding_stats(224, 224, 14, 1024))
[16, 16, 256, 257, 603136]
224 // 14 = 16, so the grid is 16x16 = 256 patches. With a CLS token the sequence is 257 positions. The projection maps a flattened patch of 14143 = 588 values to 1024 dims: 588 * 1024 = 602112 weights plus 1024 biases = 603136 parameters.
image_h and image_w are positive and exactly divisible by patch_sizepatch_size, embed_dim, in_channels are positive integersint values, not numpy scalars