Paper · 2021-05-12 · cs.CV, cs.AI, cs.LG
Segmenter: Transformer for Semantic Segmentation
Segmenter is a convolution-free semantic segmentation model: a ViT encoder over image patches, then either a per-patch linear classifier or a 2-layer mask transformer whose learnable class embeddings produce one mask per class, upsampled bilinearly. Author-reported: 53.63% mIoU on ADE20K val (Seg-L-Mask/16, multi-scale), 59.0% on Pascal Context, 81.3% on Cityscapes.
Semantic segmentationVision Transformer (ViT) backbonesObject queries (learned query slots in transformer decoders)semantic-segmentationvision-transformercomputer-vision
arXivPDFCodeMarkdown12 min read
AI-compiled by claude-opus-5-5 on 2026-10-18, not yet reviewed by a human. Every claim cites its source; check it when a detail matters.
In plain words
Semantic segmentation means giving every pixel of a photo a label: road, person, sky, chair. Whether a small patch of grey is road or wall often depends on what is elsewhere in the picture, so a model needs to see the whole scene. Convolutional networks build that wide view slowly, layer by layer, and lean on extra machinery to widen it.
Segmenter skips convolutions entirely. It cuts the image into small square patches, treats them like words in a sentence, and runs a standard Vision Transformer over them, so every patch can look at every other patch from the very first layer. The transformer's output for each patch is then turned into class scores, and those coarse patch-level scores are smoothly stretched back to full image resolution.
There are two ways to do that last step. The simple one is a single linear layer per patch. The better one adds one learned "class token" per category, lets these tokens talk to the patch tokens through two more transformer layers, and then scores each patch by how similar it is to each class token, giving one mask per class.
The practical lessons are that pre-training on ImageNet is essential, that bigger models and smaller patches both help a lot, and that smaller patches sharpen boundaries at a steep cost in compute and memory.
TL;DR
Segmenter applies an ImageNet-pre-trained ViT (or DeiT) to semantic segmentation by keeping the output token of every patch and decoding it to class scores, either with a point-wise linear layer or with a "mask transformer" that jointly processes patch tokens and K learnable class embeddings and returns K masks via a scalar product §3. Training is plain per-pixel cross-entropy with SGD and a poly schedule; stochastic depth 0.1 helps and dropout hurts §4.2 tab. 2. Accuracy grows with model size and with smaller patches (Seg-B/8 49.54 vs Seg-B/16 48.06 mIoU single-scale on ADE20K val) tab. 3. The best model, Seg-L-Mask/16, reports 53.63% multi-scale mIoU on ADE20K val, 59.0% on Pascal Context and 81.3% on Cityscapes val (author-reported) tab. 6 tab. 7 tab. 8. Relevant if you want a simple, convolution-free segmentation baseline built from off-the-shelf ViT checkpoints.
Problem
Fully convolutional encoder-decoder networks dominate semantic segmentation, but convolutions only see a local neighbourhood, while labelling a patch often requires the global image context; existing fixes (dilated convolutions, spatial pyramid pooling, attention modules on top of a CNN backbone) add specialised layers and keep a convolutional, locally biased backbone §1 §2. Applying a transformer directly to pixels is too expensive because attention cost is quadratic in sequence length §1. Segmenter casts segmentation as sequence-to-sequence over patches so that global context is available in every layer, and asks whether ViT models pre-trained for classification can be fine-tuned on moderately sized segmentation datasets §1.
Method
The model is an encoder-decoder made only of transformer layers, trained end to end with per-pixel cross-entropy; at inference, argmax over classes after upsampling gives one label per pixel §3.
Encoder §3.1
- Split the image \mathbf{x} \in \mathbb{R}^{H \times W \times C} into N = HW/P^2 patches of size P \times P, flatten each and project linearly with \mathbf{E} \in \mathbb{R}^{D \times (P^2 C)} to get \mathbf{x_0} \in \mathbb{R}^{N \times D} §3.1. (Code implements the projection as a convolution with kernel and stride P vit.py.)
- Add learnable position embeddings: \mathbf{z_0} = \mathbf{x_0} + \mathbf{pos} §3.1.
- Apply L pre-norm transformer layers eq. 1 eq. 2 with standard scaled dot-product attention eq. 3 to get \mathbf{z_L} \in \mathbb{R}^{N \times D} §3.1.
\mathbf{a_{i-1}} = \text{MSA}(\text{LN}(\mathbf{z_{i-1}})) + \mathbf{z_{i-1}} eq. 1
\mathbf{z_{i}} = \text{MLP}(\text{LN}(\mathbf{a_{i-1}})) + \mathbf{a_{i-1}} eq. 2
\text{MSA}(\mathbf{Q}, \mathbf{K}, \mathbf{V}) = \text{softmax}\left(\frac{\mathbf{QK}^T}{\sqrt{d}}\right)\mathbf{V} eq. 3
Linear decoder (baseline) §3.2
- A point-wise linear layer maps \mathbf{z_L} to patch logits \mathbf{z_{lin}} \in \mathbb{R}^{N \times K}, reshaped to H/P \times W/P \times K, bilinearly upsampled to H \times W \times K, then softmax over classes §3.2.
Mask transformer decoder §3.2 fig. 2
- Introduce K learnable class embeddings \mathbf{cls} \in \mathbb{R}^{K \times D}, randomly initialised, one per semantic class §3.2.
- Concatenate them with the patch encodings \mathbf{z_L} and run a transformer encoder of M layers over the joint sequence §3.2.
- Take the output patch embeddings \mathbf{z'_M} \in \mathbb{R}^{N \times D} (L2-normalised) and class embeddings \mathbf{c} \in \mathbb{R}^{K \times D} and compute masks by scalar product eq. 4:
\text{Masks}(\mathbf{z'_M}, \mathbf{c}) = \mathbf{z'_M}\mathbf{c}^T \in \mathbb{R}^{N \times K} eq. 4
- Reshape to H/P \times W/P \times K, bilinearly upsample to H \times W \times K, then (per the paper) softmax over classes followed by a layer norm §3.2. The code orders these steps differently; see Paper vs code.
The authors relate the design to DETR, Max-DeepLab and SOLO-v2 object embeddings and note that swapping class embeddings for object embeddings would allow panoptic segmentation; this is not evaluated §3.2.
Algorithm: Segmenter forward pass (as implemented) [§3] [code: segm/model/segmenter.py#L32-L48] [code: segm/model/decoder.py#L80-L102]
Input: image batch im (B, 3, H0, W0), K classes
1. Zero-pad im on bottom/right so H, W are multiples of P.
2. Patch-embed (conv P x P, stride P) -> (B, N, D); prepend CLS token
(and DIST token for DeiT backbones).
3. Add position embeddings, bilinearly resized to the (H/P, W/P) grid if
the grid differs from the stored one.
4. Run L pre-norm transformer blocks (GELU MLP, stochastic depth), final LayerNorm.
5. Drop the CLS (and DIST) tokens -> z_L (B, N, D).
6a. Linear decoder: logits = Linear(D -> K)(z_L), reshape to (B, K, H/P, W/P).
6b. Mask transformer:
x = Linear(D -> D)(z_L); append K class embeddings -> (B, N+K, D)
run M blocks; LayerNorm
split: patches (B, N, D), classes (B, K, D)
patches = patches @ W_patch; classes = classes @ W_class (D x D each)
L2-normalise both along D
masks = patches @ classes^T (B, N, K)
LayerNorm over the K dimension; reshape to (B, K, H/P, W/P)
7. Bilinearly upsample to (H, W); crop away the padding.
Output: per-pixel class logits (B, K, H0, W0)
Training: cross-entropy(logits, labels, ignore_index) [code: segm/engine.py#L20-L34]Training and inference recipe §4.2
- Initialise the encoder from ImageNet-pre-trained ViT (ImageNet-21k, trained with the augmentation/regularisation recipe of Steiner et al.) or DeiT (ImageNet-1k); bilinearly interpolate the pre-trained position embeddings to the fine-tuning sequence length; initialise decoders from a truncated normal §4.2.
- Loss: per-pixel cross-entropy, no class reweighting. Optimiser: SGD, weight decay 0, poly decay \gamma = \gamma_0 (1 - N_{iter}/N_{total})^{0.9} §4.2.
- Augmentation: MMSegmentation pipeline: mean subtraction, random resize with ratio in [0.5, 2.0], random horizontal flip, random crop / pad to a fixed size §4.2.
- Inference: sliding window at the training resolution; multi-scale uses scales (0.5, 0.75, 1.0, 1.25, 1.5, 1.75) plus horizontal flip, averaging results §4.2.
Hyperparameters
Model variants tab. 1 §4.2. Head size is 64, heads = token size / 64, MLP hidden size = 4 x token size §4.2.
| Model | Backbone | Layers | Token size | Heads | Params |
|---|---|---|---|---|---|
| Seg-Ti | ViT-Ti | 12 | 192 | 3 | 6M |
| Seg-S | ViT-S | 12 | 384 | 6 | 22M |
| Seg-B | ViT-B | 12 | 768 | 12 | 86M |
| Seg-B† | DeiT-B | 12 | 768 | 12 | 86M |
| Seg-L | ViT-L | 24 | 1024 | 16 | 307M |
Naming: Seg-B/16 means Base with 16x16 patches; † marks DeiT backbones; "-Mask" marks the mask transformer decoder §4.2. Patch sizes used: 8, 16, 32 §4.2.
| Name | Value | Where | Notes |
|---|---|---|---|
| Mask transformer layers M | 2 | §4.3 config.yml | 1 layer for Seg-L-Mask/16 on Cityscapes (memory) §4.4 |
| Mask transformer token / hidden size | same as encoder | §4.3 factory.py | code: heads = D/64, d_ff = 4D |
| Mask transformer dropout / stochastic depth | not stated | config.yml | code: dropout 0.1, drop_path 0.0 |
| Encoder stochastic depth | 0.1 | §4.3 tab. 2 train.py | code: linearly increasing per layer from 0 to 0.1 vit.py |
| Encoder dropout | 0 | §4.3 tab. 2 train.py | |
| Optimiser | SGD | §4.2 | momentum not stated; code 0.9 train.py |
| Weight decay | 0 | §4.2 train.py | |
| LR schedule | poly, power 0.9 | §4.2 | code adds min_lr 1e-5, no warmup train.py train.py |
| ADE20K: base LR / iterations / batch | 1e-3 / 160K / 8 | §4.2 | code: 64 epochs config.yml |
| Pascal Context: base LR / iterations / batch | 1e-3 / 80K / 16 | §4.2 | code: 256 epochs config.yml |
| Cityscapes: base LR / iterations / batch | 1e-2 / 80K / 8 | §4.2 | code: 216 epochs config.yml |
| Crop size | ADE20K 512, Pascal Context 480, Cityscapes 768 | §4.2 | Seg-L-Mask/16 on ADE20K uses 640 §4.2 |
| Random resize ratio | 0.5 to 2.0 | §4.2 ade20k.py | |
| Sliding-window size / stride | size = training size; stride not stated | §4.2 | code: ADE20K 512/512, Pascal 480/320, Cityscapes 768/512 config.yml |
| Multi-scale test scales | 0.5, 0.75, 1.0, 1.25, 1.5, 1.75 + flip | §4.2 base.py | |
| Pre-training resolution | 224, fine-tuned on ImageNet-1k at 384 (ViT-B/8 at 224) | §4.2 | |
| Decoder init | truncated normal | §4.2 | code: std 0.02, biases 0 utils.py |
| From-scratch ablation | LR 1e-3; SGD+poly or AdamW+cosine with 16K warmup (10% of iterations) | app. A |
Results
Author-reported. Ablations are on ADE20K val, single-scale, linear decoder unless stated §4.3. Throughput (Im/sec) is measured on one V100 at 512 resolution with the largest batch that fits §4.3.
Regularisation, Seg-S/16, ADE20K val mIoU tab. 2:
| Dropout \ Stochastic depth | 0.0 | 0.1 | 0.2 |
|---|---|---|---|
| 0.0 | 45.01 | 45.37 | 45.10 |
| 0.1 | 42.02 | 42.30 | 41.14 |
| 0.2 | 36.49 | 36.63 | 35.67 |
Model size and patch size, ADE20K val tab. 3:
| Method | Backbone | Patch size | Im/sec | ImNet acc. | mIoU (SS) |
|---|---|---|---|---|---|
| Seg-Ti/16 | ViT-Ti | 16 | 396 | 78.6 | 39.03 |
| Seg-S/32 | ViT-S | 32 | 1032 | 80.5 | 40.64 |
| Seg-S/16 | ViT-S | 16 | 196 | 83.7 | 45.37 |
| Seg-B†/16 | DeiT-B | 16 | 92 | 85.2 | 47.08 |
| Seg-B/32 | ViT-B | 32 | 516 | 83.3 | 43.07 |
| Seg-B/16 | ViT-B | 16 | 92 | 86.0 | 48.06 |
| Seg-B/8 | ViT-B | 8 | 7 | 85.7 | 49.54 |
| Seg-L/16 | ViT-L | 16 | 33 | 87.1 | 50.71 |
Decoder and object size, ADE20K val mIoU tab. 4:
| Method | Decoder | Small | Medium | Large | mIoU (SS) |
|---|---|---|---|---|---|
| DeepLab RNeSt-101 | UNet | 37.85 | 50.89 | 50.67 | 46.47 |
| Seg-B/32 | Linear | 31.95 | 47.82 | 49.44 | 43.07 |
| Seg-B-Mask/32 | Mask | 32.29 | 49.44 | 50.82 | 44.19 |
| Seg-B†/16 | Linear | 38.31 | 50.91 | 52.08 | 47.10 |
| Seg-B†-Mask/16 | Mask | 40.49 | 51.37 | 54.24 | 48.70 |
| Seg-B/16 | Linear | 39.57 | 51.32 | 53.28 | 48.06 |
| Seg-B-Mask/16 | Mask | 40.16 | 52.61 | 52.66 | 48.48 |
| Seg-B/8 | Linear | 41.43 | 54.35 | 52.85 | 49.54 |
| Seg-L/16 | Linear | 42.08 | 54.67 | 55.39 | 50.71 |
| Seg-L-Mask/16 | Mask | 42.02 | 54.83 | 57.06 | 51.30 |
Training-set size, Seg-S/16, ADE20K val tab. 5: 4k images 38.31, 8k 41.87, 12k 43.42, 16k 44.61, 20k 45.37 mIoU (SS). The authors read this as a marked drop below 8k images §4.3.
ImageNet pre-training, Seg-S/16, ADE20K val tab. 9 app. A:
| Pre-training | Optimizer | mIoU (SS) |
|---|---|---|
| None | AdamW | 4.42 |
| None | SGD | 12.51 |
| ImageNet-21k | AdamW | 34.77 |
| ImageNet-21k | SGD | 45.37 |
State of the art, ADE20K val tab. 6:
| Method | Backbone | Im/sec | mIoU | +MS |
|---|---|---|---|---|
| OCR | HRNetV2-W48 | 83 | - | 45.66 |
| ACNet | ResNet-101 | - | - | 45.90 |
| DNL | ResNet-101 | - | - | 45.97 |
| DRANet | ResNet-101 | - | - | 46.18 |
| CPNet | ResNet-101 | - | - | 46.27 |
| DeepLabv3+ | ResNet-101 | 76 | 45.47 | 46.35 |
| DeepLabv3+ | ResNeSt-101 | 15 | 46.47 | 47.27 |
| DeepLabv3+ | ResNeSt-200 | - | - | 48.36 |
| SETR-L MLA | ViT-L/16 | 34 | 48.64 | 50.28 |
| Swin-L UperNet | Swin-L/16 | 34 | 52.10 | 53.50 |
| Seg-B†/16 | DeiT-B/16 | 77 | 47.08 | 48.05 |
| Seg-B†-Mask/16 | DeiT-B/16 | 76 | 48.70 | 50.08 |
| Seg-L/16 | ViT-L/16 | 33 | 50.71 | 52.25 |
| Seg-L-Mask/16 | ViT-L/16 | 31 | 51.82 | 53.63 |
Pascal Context val, mIoU (MS) tab. 7:
| Method | Backbone | mIoU (MS) |
|---|---|---|
| DeepLabv3+ | ResNet-101 | 48.5 |
| DANet | ResNet-101 | 52.6 |
| ANN | ResNet101 | 52.8 |
| CPNet | ResNet-101 | 53.9 |
| CFNet | ResNet-101 | 54.0 |
| ACNet | ResNet-101 | 54.1 |
| APCNet | ResNet101 | 54.7 |
| DNL | HRNetV2-W48 | 55.3 |
| DRANet | ResNet-101 | 55.4 |
| OCR | HRNetV2-W48 | 56.2 |
| SETR-L MLA | ViT-L/16 | 55.8 |
| Seg-B†/16 | DeiT-B/16 | 53.9 |
| Seg-B†-Mask/16 | DeiT-B/16 | 55.0 |
| Seg-L/16 | ViT-L/16 | 56.5 |
| Seg-L-Mask/16 | ViT-L/16 | 59.0 |
Cityscapes val, mIoU (MS) tab. 8:
| Method | Backbone | mIoU (MS) |
|---|---|---|
| PSANet | ResNet-101 | 79.1 |
| DeepLabv3+ | Xception-71 | 79.6 |
| ANN | ResNet-101 | 79.9 |
| MDEQ | MDEQ | 80.3 |
| DeepLabv3+ | ResNeSt-101 | 80.4 |
| DNL | ResNet-101 | 80.5 |
| CCNet | ResNet-101 | 81.3 |
| Panoptic-Deeplab | Xception-71 | 81.5 |
| DeepLabv3+ | ResNeSt-200 | 82.7 |
| SETR-L PUP | ViT-L/16 | 82.2 |
| Seg-B†/16 | DeiT-B/16 | 80.5 |
| Seg-B†-Mask/16 | DeiT-B/16 | 80.6 |
| Seg-L/16 | ViT-L/16 | 80.7 |
| Seg-L-Mask/16 | ViT-L/16 | 81.3 |
Other reported findings:
- Starting from the improved ViT-B/16 weights instead of the original ones raises ADE20K mIoU from 45.69% to 48.06% §4.2.
- Speed/accuracy plot: Seg-B-Mask/16 is described as matching SETR-MLA at twice the speed and beating Swin-B on both axes; Seg-B-Mask/32 runs at 400 images/s, about four times faster than a ResNet-50 FCN baseline at similar accuracy §4.3 fig. 4.
- Qualitatively, DeepLabv3+ gives sharper boundaries, while Segmenter labels large instances more consistently and handles partial occlusion better §4.4 fig. 5. Both fail on small instances app. C fig. 12.
- Attention fields of view adapt to instance size, and some first-layer heads already attend far away app. B fig. 6 fig. 7. An SVD projection of the learned class embeddings groups related categories app. B fig. 8.
Paper vs code
Checked against rstrudel/segmenter at 20d1bfa (MIT).
- Mask normalisation order. The paper upsamples the masks, applies softmax over classes, then a layer norm §3.2. The code applies a LayerNorm over the class dimension to the N x K patch-level masks before reshaping and upsampling, and the model returns these as logits with no softmax; softmax happens only inside the cross-entropy loss during training and in window merging at inference decoder.py segmenter.py utils.py.
- Extra projections in the mask transformer. Eq. 4 is a plain product of decoder outputs. The code adds a linear input projection from encoder width to decoder width, a LayerNorm after the decoder blocks, separate learned D x D projections for patches and for class embeddings, and L2-normalises the class embeddings as well as the patches, so the masks are cosine similarities decoder.py decoder.py. The paper mentions L2 normalisation only for the patch embeddings in the text §3.2.
- Decoder dropout. The paper says all models are trained without dropout §4.3; the code's mask transformer config sets dropout 0.1 and drop_path 0.0 config.yml.
- CLS/DIST tokens. The paper's encoder sequence contains only patch tokens §3.1; the code keeps the pre-trained CLS token (and DeiT distillation token) through the encoder and drops them before decoding vit.py segmenter.py.
- Schedule details. The paper's poly formula decays to 0 §4.2; the code decays to min_lr 1e-5 and uses SGD momentum 0.9, neither stated in the paper scheduler.py train.py. Training length is configured in epochs (64 / 256 / 216), which works out to roughly the stated 160K / 80K / 80K iterations at the stated batch sizes config.yml.
- Augmentation. Beyond what §4.2 lists, the ADE20K pipeline includes MMSegmentation's PhotoMetricDistortion and a random crop with
cat_max_ratio=0.75; normalisation divides by a std as well as subtracting a mean, with mean/std chosen per backbone (0.5/0.5 for ViT, ImageNet statistics for DeiT) ade20k.py utils.py. - Cityscapes decoder depth. The paper uses a 1-layer mask transformer for Seg-L-Mask/16 on Cityscapes §4.4; the shared config defaults to 2 layers, so this must be set by hand config.yml.
Pitfalls
- The softmax-then-layer-norm step in §3.2 is internally inconsistent: a layer norm after a softmax destroys the stated property that class scores sum to 1. Follow the code (LayerNorm on the patch-level masks, softmax only in the loss / at inference).
- Pre-training is not optional. Training Seg-S/16 from scratch gives 4.42 to 12.51 mIoU versus 45.37 with ImageNet-21k weights tab. 9; and which ImageNet weights matters (45.69 vs 48.06 for ViT-B/16) §4.2.
- Dropout hurts sharply (45.37 → 36.63 with dropout 0.2) tab. 2. Make sure the encoder's dropout is really 0 when loading timm/ViT configs whose defaults may differ (the code's
VisionTransformerdefaults to dropout 0.1 and relies on the training script passing 0 vit.py train.py). - Patch size is a compute trap. Seg-B/8 gains 1.48 mIoU over Seg-B/16 but runs at 7 vs 92 images/s tab. 3. Since N = HW/P² §3.1, halving P quadruples the token count, and global attention cost grows with N².
- Position embeddings must be resized to the fine-tuning grid by bilinear interpolation §4.2; the code also re-interpolates on the fly whenever the input grid differs from the stored one, e.g. during sliding-window inference with padding vit.py. Inputs are zero-padded to a multiple of P and predictions cropped back utils.py.
- Inconsistent numbers across tables. Seg-L-Mask/16 is 51.30 (SS) in tab. 4 but 51.82 in tab. 6; the paper does not say why (plausibly the 640 vs 512 training resolution, not stated). Seg-B†/16 is 47.10 in tab. 4 vs 47.08 in tab. 3 and tab. 6. Throughput for Seg-B†/16 is 92 images/s in tab. 3 but 77 in tab. 6.
- The headline ADE20K comparison is not like-for-like in resolution: the best model trains at 640x640 while most Segmenter rows use 512 §4.2; its multi-scale margin over Swin-L UperNet is 0.13 mIoU tab. 6.
- The mask transformer does not always help per bucket: Seg-B-Mask/16 is worse than the linear Seg-B/16 on large objects (52.66 vs 53.28) tab. 4, although overall mIoU improves.
- Learning-rate remark is unclear. §4.2 says the schedule is like DeepLabv3+ "with learning rates divided by a factor 10 except for Cityscapes"; use the explicit values (1e-3, 1e-3, 1e-2) §4.2.
- Small datasets degrade results: below about 8k training images ADE20K mIoU drops noticeably tab. 5; Segmenter is less dominant on the smaller Cityscapes benchmark tab. 8.
- Sliding-window stride is not stated in the paper; the ADE20K config uses non-overlapping windows (stride = window size) config.yml.
Lineage
- Builds on: ViT (arXiv 2010.11929) for the encoder and DeiT (arXiv 2012.12877) for ImageNet-1k weights; improved ViT training recipe from Steiner et al. (arXiv 2106.10270) for initialisation; the mask transformer is inspired by DETR object queries, Max-DeepLab and SOLO-v2 §2 §3.2 §4.2.
- Compared against: SETR (ViT backbone with CNN-style decoders), Swin Transformer + UperNet, DeepLabv3+ with ResNet/ResNeSt backbones, OCR, DANet, CCNet and other FCN variants tab. 6 tab. 7 tab. 8.
- Superseded by: not known from content in this wiki.
Source and license
Adapted from “Segmenter: Transformer for Semantic Segmentation” by Robin Strudel, Ricardo Garcia, Ivan Laptev, Cordelia Schmid (arXiv:2105.05633), used under CC BY 4.0. Changes: condensed and restructured for implementation, with tables, equations and algorithms taken from the paper and its code, each cited to its source. Not affiliated with or endorsed by the authors. Code citations refer to https://github.com/rstrudel/segmenter at commit 20d1bfad3541 (MIT). Any quoted code is shown under that license.