SAM, explained · Part 3 of 3 · Covers Figure 4, §3, Appendix A, Figure 14
The Model: Encoder, Prompts and Decoder
How SAM turns a photo and a click into three masks and three scores. We open the released model, follow each tensor through it, and compute its training loss by hand.
Segment Anything. Alexander Kirillov, Eric Mintun, Nikhila Ravi, Hanzi Mao, Chloe Rolland, Laura Gustafson, Tete Xiao, Spencer Whitehead, Alexander C. Berg, Wan-Yen Lo, Piotr Dollár, Ross Girshick. ICCV 2023, 2023. arXiv:2304.02643
Part 1 showed what SAM does: a photo and a click go in, and three masks with three scores come out. Now we open the model and follow the numbers through each of its three blocks, with the released weights of SAM ViT-H.
Section 3 names the three components with one adjective each: "an image encoder, a flexible prompt encoder, and a fast mask decoder". The encoder gets no adjective, because it may be slow: it runs once for each picture. Section 3 gives the summary and Appendix A the details, so we read the two together.
The figure below is Figure 4 again, with the real shapes that we measured in the released model. Each number in it comes from our script.
SAM ViT-H with the real shapes. The image encoder runs once: 3 × 1024 × 1024 pixels become 64 × 64 patch vectors of 1,280 numbers, then pass through 32 transformer blocks and a neck, and give the 256 × 64 × 64 image embedding. For each prompt, the prompt encoder makes tokens, and the mask decoder makes three masks of 256 × 256 and three scores.
The encoder is a ViT-H/16. Decode the name: H means "Huge", the largest of the three sizes, and /16 means patches of 16 × 16 pixels. Appendix A gives the details:
Each of the 32 blocks has one attention step. It is the only step in which a token gets information from other tokens, so we read its equation carefully.
Think of a library. Each token writes a short question (its query). Each token also has a label on its spine (its key) and some content inside (its value). A token compares its question with every label. It then takes a mix of the contents, with the most weight on the best matches.
Attention(Q,K,V)=softmax(dQK⊤)V
Read it from left to right:
Q, K and V are tables with one row for each token: the queries, the keys and the values. Each row has d numbers. The network makes them from the tokens with three learned matrices.
QK⊤ multiplies each query with each key (a dot product). The result is a table of scores: one score for each pair of tokens. A large score means "this label matches my question".
d divides the scores to keep them in a moderate range when d is large.
softmax changes each row of scores into weights that are positive and add up to 1.
The weights multiply V. Each token gets a weighted average of the values: its new information.
A tiny example with d=2, one query and two keys. Let q=(1,0), k1=(2,0) and k2=(0,2). The scores are q⋅k1/2=1.414 and q⋅k2/2=0. The softmax gives the weights:
e1.414+e0e1.414=0.804,e1.414+e0e0=0.196
With the values v1=(10,0) and v2=(0,10), the output is 0.804×(10,0)+0.196×(0,10)=(8.04,1.96). The query matched k1 better, so the output is mostly v1.
In ViT-H, each block runs 16 copies of this step side by side, the attention heads, each with d=80 and its own matrices. The real cost is in the size of the score table. With global attention, each of the 4,096 tokens compares itself with all 4,096 tokens.
All three use windows of 14 × 14 tokens, and all three have exactly four global blocks, spaced evenly. In ViT-H, the global blocks are blocks 8, 16, 24 and 32 (the configuration counts from 0). So the tokens work in their rooms for seven blocks, then meet in the hall once, four times in total.
Why windows of 14 × 14? MAE trained the encoder on pictures of 224 × 224 pixels, which is 14 × 14 patches. A window of 14 × 14 tokens is exactly the view that the encoder knew from pre-training. The paper does not say this, but it is the reason in the ViTDet paper [62] that SAM follows.
There is one small problem: 64 is not a multiple of 14. So each windowed block first pads the grid to 70 × 70 tokens with zeros. It then cuts the grid into 5 × 5 = 25 windows:
plain text
windowed attention: the 64 x 64 grid is padded to 70 x 70 (804 padding cells), then cut into 5 x 5 = 25 windows of 14 x 14 = 196 tokens score pairs per head: windowed 25 x 196^2 = 960,400; global 4096^2 = 16,777,216; ratio 17.5
Left: the 64 × 64 grid of tokens, padded to 70 × 70 (red strips) and cut into 25 windows of 14 × 14 tokens. One window is highlighted. Right: one global block computes 16,777,216 attention scores per head; one windowed block computes 960,400, 17.5 times fewer. Blocks 8, 16, 24 and 32 of ViT-H are global.
The saving in attention scores is 17.5 times. But attention scores are only part of the work of a block. The MLP and the three matrices that make Q, K and V do the same work in both kinds of block. We counted the FLOPs of one block of each kind:
plain text
one windowed block 176.9 GFLOPs, one global block 248.3 GFLOPs (1.40 x) of which attention scores and mixing (2 matrix products, 2 FLOPs per multiply-add): global 4 x 4096^2 x 1280 = 85.9 GFLOPs, windowed 4 x 25 x 196^2 x 1280 = 4.9 GFLOPsif all 32 blocks were global: 7.96 TFLOPs (1.34 x)
A global block costs 1.4 times a windowed block. With global attention in all 32 blocks, the encoder would need 7.96 TFLOPs in place of 5.96, about 34% more. On the GPU of our laptop, the difference is larger: a global block takes about 2.7 times as long as a windowed block (below). The large table of attention scores also needs much memory.
Windows have a cost: a token in one room cannot see a token in the next room. Think of a cat whose head is in one window and whose tail is in another. The four global blocks fix this. After each global block, every token carries some news from the whole picture back into its room.
We followed one picture through the encoder of ViT-H and printed the shape of the data after each step:
plain text
pixel values (1, 3, 1024, 1024) 3,145,728 numbers patch embedding (16 x 16 conv, stride 16) (1, 64, 64, 1280) 5,242,880 numbers + absolute position embedding (1, 64, 64, 1280) 5,242,880 numbers windowed block: pad 64 -> 70, cut into windows (25, 14, 14, 1280) 6,272,000 numbers after 32 blocks (1, 64, 64, 1280) 5,242,880 numbers neck: 1 x 1 conv 1280 -> 256 + layer norm (1, 256, 64, 64) 1,048,576 numbers neck: 3 x 3 conv 256 -> 256 + layer norm (1, 256, 64, 64) 1,048,576 numbers our step-by-step embedding vs get_image_embeddings: max difference 0.00e+00
The 3 colour numbers of each of the 1024 × 1024 pixels become 4,096 tokens of 1,280 numbers. The encoder adds a learned position vector to each token. Inside a windowed block, the grid becomes 25 separate windows; the extra numbers are the padding. After the 32 blocks, the grid has the same shape as before. But each token now describes its place in the context of the whole picture. Our step-by-step run gives exactly the same embedding as the library function.
The weights of the encoder follow the same plan. Each of the 32 blocks has almost 20 million weights:
How do you change a position, two numbers, into 256 numbers that a network can use? SAM uses random Fourier features, from the paper cited as [95] (Tancik et al., 2020).
Here is the idea. Lay many striped patterns over the picture, like zebra stripes, each at its own angle and width. For each pattern, write down where the point falls inside its stripe: at the start, the middle or the end. One pattern alone does not identify the point, because the stripes repeat. But many patterns together give a code that differs for every place. It works like a clock with many hands at different speeds: the set of all hand positions tells the exact time.
The formula is:
γ(c)=[sin(2πc⊤G),cos(2πc⊤G)],c=(2u−1,2v−1)
Read it from right to left this time:
u and v are the position of the point as fractions of the 1024 × 1024 square. They go from 0 at the left or top edge to 1 at the right or bottom edge. c moves them to the range from −1 to +1.
G is a table of 2 rows and 128 columns. Each column g is one striped pattern: its direction and its number of stripes. The numbers of G are random. They are drawn once, before training, and never change.
c⊤G multiplies the point with each column: 128 numbers. Each one says "how far along this pattern" the point is.
2π changes "how far" into an angle, and sin and cos give the position inside the stripe.
γ(c) joins the 128 sines and the 128 cosines: 256 numbers.
A tiny example in one dimension with one pattern, g=3. The angle is 2π×3×x:
point x
angle
(sin,cos)
0.10
1.885
(+0.951, −0.309)
0.12
2.262
(+0.771, −0.637)
0.30
5.655
(−0.588, +0.809)
The two near points get similar pairs. The far point gets a very different pair. This is what we want from a code for positions.
Now the real click on the cat's ear, at pixel (275, 62) of the photo. Our script follows it through each step:
plain text
random matrix G: shape (2, 128), mean -0.100, std 1.043 (drawn once at random, then kept fixed)click (275, 62) in the photo -> x 1.6 -> (440.0, 99.2) in the 1024 frame -> +0.5 (pixel centre) / 1024 -> (0.4302, 0.0974) -> 2u - 1 -> (-0.1396, -0.8053) column 0: g = (-0.483, +1.120); c . g = -0.1396 x -0.483 + -0.8053 x +1.120 = -0.8343; angle 2 pi x -0.8343 = -5.242; sin +0.863, cos +0.505 column 1: g = (-0.663, -0.098); c . g = -0.1396 x -0.663 + -0.8053 x -0.098 = +0.1717; angle 2 pi x +0.1717 = +1.079; sin +0.881, cos +0.472 column 2: g = (+0.062, -0.613); c . g = -0.1396 x +0.062 + -0.8053 x -0.613 = +0.4848; angle 2 pi x +0.4848 = +3.046; sin +0.095, cos -0.995positional encoding: 128 sines + 128 cosines = 256 numbers; first four: +0.863, +0.881, +0.095, +0.977stripe width (period) of each of the 128 frequencies, in pixels of the 1024 frame: shortest 157.8, median 400.1, longest 5188
The click is first scaled by 1.6, as in Part 1, to (440.0,99.2). SAM adds 0.5 to aim at the centre of the pixel and divides by 1024. That gives (u,v)=(0.4302,0.0974), and then c=(−0.1396,−0.8053). For the first column of G:
The stripes have widths from 158 to 5,188 pixels. The narrow stripes separate places that are close together. The wide stripes tell the left half of the picture from the right half. So the code is exact at small scales and at large scales.
Does the code behave as promised? We compared the code of the click with the code of every cell of the 64 × 64 grid:
The cosine similarity between the code of the click and the code of each place of the photo. It is 0.999 at the click, 0.979 one cell (16 pixels) away, 0.701 four cells away and −0.016 sixteen cells away. Near places have similar codes; far places have unrelated codes.
The same table G also makes the codes of the 64 × 64 cells of the image embedding. So a click and the cell under it get almost the same code, and the decoder can match them. This shared table is the bridge between the prompt and the picture.
The script then compared our hand-made code with the model's own prompt tokens:
plain text
sparse prompt embedding from the model: (1, 1, 2, 256) (image, prompts, tokens, 256): the click + one padding token token 1 = positional encoding + learned "foreground point" vector: max difference to ours 2.38e-07 token 2 = the learned "not a point" vector (padding): max difference 0.00e+00dense prompt embedding (no mask given): (1, 256, 64, 64): one learned "no mask" vector copied to all 64 x 64 cells
The first token is our positional code plus the learned "foreground point" vector, to the seventh decimal place. The second token is a surprise. With a click and no box, SAM adds a padding token: a learned "not a point" vector. The original code pads point prompts in this way when no box is present. It keeps the input in the shape that the decoder saw in training.
mask prompt 256 x 256: conv 2x2 stride 2 -> (1, 4, 128, 128); conv 2x2 stride 2 -> (1, 16, 64, 64); conv 1x1 -> (1, 256, 64, 64); then added to the image embedding cell by cellweights of the prompt encoder: random matrix G (2 x 128) 256, point and corner types (4 x 256) 1,024, "not a point" (256) 256, "no mask" (256) 256, mask convolutions 4,684; total 6,476
That is the whole prompt encoder: 6,476 numbers, and 256 of them are the random table G. Part 1 counted the same total. A single block of the image encoder has about 3,000 times more weights.
Seven tokens on one side, 4,096 image vectors on the other side. Now the four steps:
One layer of the mask decoder. Steps 1 to 3 update the 7 tokens: they talk to each other, read the 4,096 image vectors, and each passes through an MLP. Step 4 updates the image: each image vector reads the tokens. The decoder runs this layer two times.
To check that we understand the four steps, we wrote the decoder again by hand, with the model's own weights. Then we compared our result with the model's output. The core is a few lines:
Here q is the 7 tokens and keys is the 4,096 image vectors. tokens is the original tokens, and kpe is the positional code of each image cell. (In the first layer, step 1 has no residual and no added code; the script handles this case.) The output:
plain text
layer 1, step 1: self-attention among the 7 tokens (1, 7, 256) layer 1, step 2: tokens attend to the 4,096 image vectors (1, 7, 256) layer 1, step 3: MLP on each token (1, 7, 256) layer 1, step 4: image vectors attend to the tokens (1, 4096, 256) layer 2, step 1: self-attention among the 7 tokens (1, 7, 256) layer 2, step 2: tokens attend to the 4,096 image vectors (1, 7, 256) layer 2, step 3: MLP on each token (1, 7, 256) layer 2, step 4: image vectors attend to the tokens (1, 4096, 256) final: tokens attend to the image once more (1, 7, 256) upscale: transposed conv 2x2, stride 2 (1, 64, 128, 128) upscale: transposed conv 2x2, stride 2 (1, 32, 256, 256) 4 mask tokens through their MLPs (1, 4, 32) dot product with every pixel vector -> 4 masks (1, 4, 256, 256) IoU token through the IoU head -> 4 scores (1, 4)our hand-written decoder vs the model: masks max difference 3.66e-04, scores max difference 1.19e-07
Our masks agree with the model's masks to 0.0004, on mask values that range up to about 34. The small difference comes from the order of the GPU's additions. So the four steps in the paper are the whole decoder.
Look at the code again. Every attention call adds tokens to the token side and kpe to the image side. The paper explains why:
We counted the weights of each piece of the decoder:
plain text
weights of the mask decoder: output tokens (1 IoU + 4 mask, x 256) 1,280 two-way layer 1 1,579,520 two-way layer 2 1,579,520 final token-to-image attention + norm 132,224 upscaling (2 transposed convs + norm) 73,952 4 mask MLPs (256 -> 256 -> 256 -> 32) 559,232 IoU head (256 -> 256 -> 256 -> 4) 132,612 total 4,058,340 inside one two-way layer: self-attention 263,168, token-to-image attention 131,712, MLP 256 -> 2048 -> 256 1,050,880, image-to-token attention 131,712, 4 layer norms 2,048attention widths: self-attention q, k, v = 256 (8 heads of 32); cross-attention q, k, v = 128 (8 heads of 16)
Appendix A gives the sizes behind these counts: width 256, an MLP of 2,048 numbers inside, and 8 heads in each attention. The MLP holds two thirds of each layer's weights, but it touches only the 7 tokens, so it is cheap. The two cross-attentions touch all 4,096 image vectors, so the team halved their width to 128. The whole decoder, 4.06 million weights, is smaller than one block of the image encoder (19.7 million).
After the two layers, the decoder has 7 updated tokens and an updated 64 × 64 grid. One cell of the grid covers 16 × 16 pixels, which is too coarse for an exact edge. So the decoder first makes the grid larger:
The dot product is the "dynamic linear classifier" of §3. Here is its equation, for mask k at pixel (i,j):
Think of a paint-matching card. The mask token describes the object that the user wants, as 32 numbers: the card. Each pixel has its own description, also 32 numbers. At each pixel, SAM holds the card next to the pixel and asks "how well do they match?". The dot product is that match.
Read it from left to right:
mk(i,j) is the raw score of mask k at pixel (i,j), the logit. Positive means "inside", negative means "outside".
wk is the card: the 32 numbers that the MLP made from mask token k. It is the same at all pixels, and it changes with the prompt.
f(i,j) is the description of the pixel: the 32 numbers of the upscaled grid. It is the same for all masks.
The sum multiplies the 32 pairs and adds the products.
σ, the sigmoid, changes the logit into a probability between 0 and 1. A logit of 0 gives 0.5.
A tiny example with 3 numbers in place of 32. Let the card be w=(2,−1,0.5). A pixel with f=(0.3,0.2,0.4) gets m=0.6−0.2+0.2=0.6, so p=σ(0.6)=0.646: probably inside. A pixel with f=(−0.1,0.5,0) gets m=−0.2−0.5+0=−0.7, so p=0.332: probably outside.
Now the real numbers from the ear click, at three pixels of the photo:
plain text
the dynamic dot product: mask logit at pixel = (32 numbers from the mask token) . (32 numbers of the upscaled pixel) mask token 1, first 4 numbers w = -139.524, -5.909, -194.530, +5.933; pixel at the ear, first 4 numbers f = -0.053, +0.129, -0.145, +0.372 the 3 largest products w_i f_i: i=20: +278.544 x +0.109 = +30.370, i=2: -194.530 x -0.145 = +28.284, i=13: +191.332 x +0.139 = +26.634 ear photo (275, 62) -> 256 grid (110, 24): mask 1 (ear) logit +33.97 -> p 1.000; mask 3 (cat) logit +9.90 -> p 1.000 cat body photo (120, 220) -> 256 grid ( 48, 88): mask 1 (ear) logit -22.51 -> p 0.000; mask 3 (cat) logit +4.14 -> p 0.984 blanket photo (250, 400) -> 256 grid (100, 160): mask 1 (ear) logit -35.86 -> p 0.000; mask 3 (cat) logit -10.85 -> p 0.000
The dynamic dot product at three pixels. At the ear, both the ear mask and the cat mask are positive. On the cat's body, only the cat mask is positive (+4.14). On the blanket, both are negative. The pixel descriptions are the same for every mask; only the 32 numbers of the mask token change.
The table reads like a sentence. At the ear, both cards match: the ear is part of the ear and part of the cat. On the body, the ear card says "no" (−22.51) and the cat card says "yes" (+4.14). On the blanket, both cards say "no". The pixel descriptions did not change between the two masks. Only the card changed.
The card's numbers are large (−139.5, −194.5) and the pixel numbers are small (−0.053, −0.145). Their products are large: the three largest products alone add up to more than 85. So the logits are far from 0 at most pixels, and the sigmoid gives almost exactly 0 or 1. The uncertain pixels are only near the edges.
Where do the mask tokens look? The last token-to-image attention shows it. We averaged its 8 heads for each mask token:
The final token-to-image attention of the three mask tokens, averaged over the 8 heads (brighter = more attention, square-root scale). The ear tokens 1 and 2 put up to 0.217 of their attention on one cell near the click. The cat token 3 spreads its attention: its strongest cell gets only 0.023.
The ear tokens look at one small place. The cat token spreads its attention over the cat, because its answer is large. Nobody told the tokens to do this. Training discovered it.
We can see the blur with real numbers. Take two valid answers for the ear click: "the ear" and "the cat". Average SAM's probabilities for its ear mask and its cat mask, as a single-output model would learn to do:
Two valid answers for one click, and their average. The average is sure about the ear but unsure about the rest of the cat: 46,982 pixels have a probability between 0.4 and 0.6.
plain text
one output that averages "ear" and "cat" (probability 0.5 on the cat body): against the ear: mask loss 1.3399 (best of the three masks: 0.0152) against the cat: mask loss 0.5388 (best of the three masks: 0.0471) pixels with a probability between 0.4 and 0.6: 46,982 (a blurred, unsure mask)
The averaged mask is bad for both meanings. Its loss (defined below) is 88 times the best of SAM's three masks if the user wanted the ear. It is 11 times worse if the user wanted the cat. Three outputs remove the problem.
Appendix A gives the details:
And one more detail explains the strange fourth mask token:
We printed all four masks for the single ear click:
plain text
the four mask tokens for the single ear click (token 0 is the single-mask output for several prompts): token 0: predicted IoU 0.926 465 pixels token 1: predicted IoU 0.946 427 pixels token 2: predicted IoU 0.944 470 pixels token 3: predicted IoU 0.980 49,520 pixels
All four mask tokens for the one click on the ear. Tokens 1 to 3 are the three masks that SAM returns for one click. Token 0 is the single-mask slot for several clicks; for one click, it gives the ear (465 pixels).
Token 0 chose the ear. Training uses this token for several prompts, so SAM does not return it for one click. Tokens 1, 2 and 3 are the masks that Part 1 showed.
We counted the operations of each part and timed them on our laptop:
plain text
image encoder ViT-H: 5.96 TFLOPs per pictureprompt encoder + mask decoder: 3.62 GFLOPs per prompt = 0.06% of the encoderMPS image encoder 2999.4 ms (patch embed 7.0, windowed block 81.9, global block 217.5, neck 2.7) prompt encoder 0.40 ms mask decoder 6.02 ms both 6.49 msCPU image encoder 6779.2 ms (patch embed 19.2, windowed block 245.7, global block 358.0, neck 8.7) prompt encoder 0.05 ms mask decoder 14.44 ms both 15.82 ms
Top: the work for one picture and one click. The image encoder needs 5.96 trillion operations; the prompt encoder and the mask decoder need 3.62 billion, 0.06% of the encoder. Bottom: on the laptop GPU, almost all the time goes to the 32 blocks of the encoder.
The encoder does about 1,650 times more work than the decoder. Appendix A says that the decoder "requires less than 1% of the image encoder's compute"; our count gives 0.06%, so "less than 1%" is modest. On the CPU, the prompt encoder and the decoder together take 16 ms, well under the paper's 50 ms in a browser.
Look inside the encoder's time. A windowed block takes 82 ms on the GPU, and a global block takes 218 ms. The four global blocks are one eighth of the blocks, but they take more than a quarter of the encoder's time. With 32 global blocks, the encoder would be more than twice as slow on this computer.
The Background page gave this loss in short. Now we take each part in turn, and then compute all of it on SAM's real masks.
We need a correct mask for that. Our photo has no hand-drawn mask, so we made two stand-in references with SAM itself, from box prompts. "The ear" is SAM's mask for a tight box around the ear (465 pixels). "The cat" is SAM's mask for a box around the left cat (50,517 pixels). The masks that we grade are the three masks of the single ear click.
Most pixels are easy. For our ear click, more than 99% of the photo is clearly "not the ear". Plain cross-entropy charges a small amount for each of these easy pixels, and hundreds of thousands of small amounts add up. The few hard pixels at the edge, where the model is really unsure, get lost in the crowd. Focal loss turns down the easy pixels.
p is the probability that SAM gives to "inside" at one pixel, and y is the correct answer (1 inside, 0 outside). pt is the probability of the correct answer. If the pixel is outside and p=0.2, then pt=0.8.
−logpt is the cross-entropy of the Background page: near 0 when pt is near 1, and large when pt is small.
(1−pt)γ is the new factor, with γ=2. For an easy pixel, pt is near 1, so the factor is near 0. For a hard pixel, the factor is near 1.
αt is a fixed weight for the two classes: α=0.25 for "inside" pixels and 1−α=0.75 for "outside" pixels. The paper does not give α or γ. We use γ=2 and α=0.25, the values of the focal-loss paper [65] and of the code that SAM follows.
The loss of the mask is the average over all pixels.
Three pixels show the effect:
pixel
pt
cross-entropy −logpt
(1−pt)2
focal loss
easy, inside, p=0.9
0.9
0.1054
0.0100
0.25×0.0100×0.1054=0.00026
hard, inside, p=0.3
0.3
1.2040
0.4900
0.25×0.4900×1.2040=0.14749
wrong, outside, p=0.6
0.4
0.9163
0.3600
0.75×0.3600×0.9163=0.24740
In cross-entropy, the hard pixel costs 11 times the easy one. In focal loss, it costs about 560 times as much. The easy pixel almost disappears from the sum.
The loss of one pixel against p_t, the probability that the model gives to the correct answer (without the factor α). Cross-entropy still charges about 0.1 at p_t = 90%. Focal loss charges almost nothing once p_t is above 70%, and keeps most of the penalty for the hard pixels on the left.
Focal loss looks at one pixel at a time. It does not care about the size of the object. A small ear of 465 pixels is less than 0.2% of the 307,200 pixels of the photo. So even a missing ear changes the average focal loss very little. Dice loss looks at the whole mask at once.
Ldice=1−∑ipi+∑iyi+12∑ipiyi+1
Read it from the inside:
∑ipiyi adds the probabilities at the pixels that are truly inside. It is a soft version of the overlap ∣A∩B∣ of Part 1.
∑ipi+∑iyi is the soft size of the predicted mask plus the size of the correct mask.
The fraction is the dice score. It is 1 for a perfect mask and 0 for no overlap. It is a close relative of IoU: 2∣A∩B∣/(∣A∣+∣B∣).
The "+1" on the top and the bottom keeps the fraction defined when both masks are empty.
1− the score changes "good" into "loss".
A tiny example with 4 pixels: p=(0.9,0.8,0.3,0.1) and y=(1,1,1,0). Without the +1:
The dice loss does not depend on the size of the picture. A mask that misses half of a small ear loses about half of its dice score. A mask that misses half of a cat loses the same. So dice loss protects the small objects, and focal loss sharpens the edges.
s^ is the score that the IoU head predicts for mask M. IoU(M,Y) is the real IoU of M with the correct mask Y, after the threshold at p=0.5. The square of the difference is the mean squared error: a predicted 0.90 for a real IoU of 0.80 costs (0.90−0.80)2=0.0100.
The first part is the mask loss of the best mask only. The second part grades the IoU head. The paper does not say whether the IoU term uses all three masks or only the best one. We take the average of all three, because the head must score every mask that it returns.
Now the real numbers. The same three masks, graded against the two references:
plain text
target: the ear mask pixels focal 20 x focal dice mask loss true IoU pred. IoU (pred - true)^2 CE (for comparison) 1 427 0.0001 0.0027 0.0472 0.0498 0.914 0.946 0.0010 0.0006 2 470 0.0000 0.0006 0.0145 0.0152 0.981 0.944 0.0013 0.0001 3 49,520 0.4899 9.7974 0.9820 10.7793 0.009 0.980 0.9431 0.6826 the minimum mask loss is mask 2: 0.0152. Only mask 2 gets a gradient from the mask loss. IoU term (mean over the 3 masks): 0.3152; total = 0.0152 + 1.0 x 0.3152 = 0.3303target: the cat mask pixels focal 20 x focal dice mask loss true IoU pred. IoU (pred - true)^2 CE (for comparison) 1 427 1.0322 20.6436 0.9839 21.6275 0.008 0.946 0.8804 4.1283 2 470 0.9960 19.9192 0.9819 20.9011 0.009 0.944 0.8750 3.9836 3 49,520 0.0008 0.0157 0.0314 0.0471 0.976 0.980 0.0000 0.0128 the minimum mask loss is mask 3: 0.0471. Only mask 3 gets a gradient from the mask loss. IoU term (mean over the 3 masks): 0.5851; total = 0.0471 + 1.0 x 0.5851 = 0.6323
The mask loss of each of the three masks, on a log scale. If the user wanted the ear, mask 2 wins with 0.0152. If the user wanted the cat, mask 3 wins with 0.0471. The other two masks get no gradient from this example.
Read the table in four steps.
The minimum moves with the target. The same three masks get the minimum at mask 2 for the ear, and at mask 3 for the cat. The two wrong masks have losses of 10.8 and about 21. With a plain sum over the three masks, these large numbers would force every mask toward the same answer. The minimum ignores them. This is the whole trick of ambiguity in one line.
Why the factor 20? The focal loss is an average over 307,200 pixels, and most of them are easy, so it is usually tiny. For mask 3 against the cat, it is 0.0008, and the dice loss is 0.0314. Times 20, the focal loss becomes 0.0157, the same size as the dice loss. For a bad mask, the focal part dominates: 20.64 against 0.98 for mask 1 against the cat.
The IoU term is large here. Mask 3 predicted 0.980, but its IoU with "the ear" is only 0.009. That costs 0.94 alone. So the IoU head is punished whenever the annotator wanted a different level of the nest, and it cannot know which level that is. It learns the expected IoU over the possible meanings. That is why the scores rank the masks well, but do not identify the one "right" mask.
We played the same game with the released model: the target is the left cat, and each round follows the rules above.
plain text
target: the left cat (50,517 pixels). Rounds without a new point: [8, 11] round 1: first point (233, 105), foreground points 1 IoU 0.953 predicted 1.005 round 2: point (188, 298) from the error region (2,391 px), foreground (missed pixel) points 2 IoU 0.995 predicted 1.015 round 3: point (77, 164) from the error region (253 px), background (extra pixel) points 3 IoU 0.995 predicted 1.002 ... round 8: no new point: only the previous mask points 7 IoU 0.996 predicted 1.001 ... round 11: no new point: only the previous mask points 9 IoU 0.996 predicted 1.002noisy box for training: sides 312 x 419 px -> noise std 20.0, 20.0 px (10% of the side, at most 20); box [5, 53, 317, 472] -> [1, 13, 312, 455]; IoU with the target 0.995
Rounds 1, 2 and 11 of the training game. Green points are foreground points (missed pixels), red points are background points (extra pixels). One correction point lifts the IoU from 0.953 to 0.995.
The first random click gives an IoU of 0.953. The first mask missed the tail: the error region has 2,391 pixels, and the second point lands on the tail. After it, the IoU is 0.995 and the error region shrinks to about 250 pixels at the edge. The later rounds change little. Our target is SAM's own mask, so the released model starts close to it. During training, with real annotated masks and a model that is still learning, the errors are much larger.
Look also at the column "predicted". The IoU head says 1.005 and 1.015. A true IoU can never be above 1. The IoU head is a plain MLP with no limit at the end, so it can overshoot a little when it is very sure.
The training recipe completes the appendix:
Our script did the arithmetic and drew the schedule:
plain text
90,000 iterations x 256 images = 23,040,000 pictures seen = 2.09 passes over 11M picturesmasks: up to 64 per GPU, 1 picture per GPU (256 pictures on 256 GPUs) -> up to 704 decoder runs per picture per iteration (64 masks x 11 rounds)
The learning rate of SAM training. It rises to 0.0008 in the first 250 iterations, stays there until iteration 60,000, drops to 0.00008, and drops again to 0.000008 at iteration 86,666, until the end at 90,000.
The layer-wise decay gives each block of the encoder its own learning rate. The paper gives only the factor 0.8. With the usual rule of the MAE code, each block gets 0.8 times the rate of the block above it:
plain text
mask decoder and neck 8e-4 x 0.8^0 = 8.00e-04 block 32 (last) 8e-4 x 0.8^1 = 6.40e-04 block 16 8e-4 x 0.8^17 = 1.80e-05 block 1 (first) 8e-4 x 0.8^32 = 6.34e-07 patch embedding 8e-4 x 0.8^33 = 5.07e-07
The first block learns about 1,260 times more slowly than the decoder. MAE already taught the early blocks to see edges and textures, and SAM keeps that knowledge almost unchanged. The late blocks and the new decoder do most of the learning.
Next, in Part 4: the data engine and SA-1B. How the three stages of annotation worked, how SAM made 1.1 billion masks by itself, and how the team checked their quality.
Run it yourself
The script code/papers/sam/sam_part3.py reads the configuration and counts the weights of the three models. It follows the shapes through ViT-H and computes the positional code of the click by hand. It runs the mask decoder again by hand, counts the FLOPs and times each part. It also computes the losses and plays the 11-round training game. Every number of this part is in results/part3.json.
bash
pip install torch transformers datasets pillowpython sam_part3.py # uses facebook/sam-vit-base, -large and -huge (about 3.8 GB in total)
The first part of the output of sam_part3.py, as it ran for this article.