Implement one multi-head Perceiver Resampler block: a fixed set of learned latent tokens cross-attends to image features, with the latents themselves concatenated onto the keys and values, followed by an output projection and a residual connection.
Flamingo's Perceiver Resampler turns a variable-length grid of visual features into a fixed number of tokens. It is not plain cross-attention - the paper's block builds keys and values from the image features with the latents appended:
K=[Xf;L]WK,V=[Xf;L]WV,Q=LWQ
so the latents can attend to each other as well as to the image, mixing information across the latent set within a single block. The key/value length is therefore n_feats + n_latents, while the query length - and so the output length - stays n_latents.
The rest is standard multi-head attention. Split the d-wide projections into h heads of width d_h = d / h, run scaled dot-product attention per head (scaling by 1/sqrt(d_h), the head width, not the model width), concatenate the heads back to d, apply W_O, and add the residual:
out=Concat(head1..headh)WO+L
Head splitting is a reshape of the last axis into (h, d_h): head i owns columns **id_h ... (i+1)d_h - 1, contiguously.
Implement:
def resampler_forward(latents, feats, Wq, Wk, Wv, Wo, num_heads):
Return the (n_latents, d) output as a nested list, every entry rounded to 4 decimals.
A nested list of n_latents rows of d floats, each rounded to 4 decimals.
L = [[1.0, 0.0], [0.0, 1.0]]
X = [[1.0, 1.0], [2.0, 0.0]]
I2 = [[1.0, 0.0], [0.0, 1.0]]
print(resampler_forward(L, X, I2, I2, I2, I2, 1))
Output:
[[2.3395, 0.3302], [0.8302, 1.6698]]
L = [[1.0, 0.0], [0.0, 1.0]] X = [[1.0, 1.0], [2.0, 0.0]] I2 = [[1.0, 0.0], [0.0, 1.0]] print(resampler_forward(L, X, I2, I2, I2, I2, 1))
[[2.3395, 0.3302], [0.8302, 1.6698]]
With identity projections the keys/values are the four rows [[1,1],[2,0],[1,0],[0,1]] - the two features followed by the two latents. Latent 0 scores them at [1,2,1,0]/sqrt(2), softmaxes to [0.2212,0.4486,0.2212,0.1091], mixes the values to [1.3396,0.3302] and adds the residual [1,0] to give [2.3395,0.3302]. Dropping the latents from the key set would change every weight.
concat([feats, latents], axis=0) - features FIRST, latents secondnum_heads contiguous slices of the last axis; d % num_heads == 01 / sqrt(d / num_heads), the per-HEAD widthlatents AFTER the output projection-0.0