Scaled Dot-Product Cross-Attention
Problem Statement
Compute the output of a single-head cross-attention layer given explicit query, key and value matrices - the operation that lets learned queries (or text tokens) read from image features.
Background
Cross-attention is the same computation as self-attention except that the queries come from one stream and the keys/values from another. In a VLM the queries are language-side (or the Q-Former's learned queries) and the keys/values are the vision encoder's patch features:
with Q of shape (n_q, d_k), K of shape (n_kv, d_k), V of shape (n_kv, d_v), and the output of shape (n_q, d_v). The output length follows the queries, not the keys - which is exactly why a resampler with 64 queries turns 576 patch features into 64 tokens.
Two things trip people up:
- The 1/sqrt(d_k) scale is not cosmetic. Dot products of d_k independent terms grow like sqrt(d_k); without the divisor the softmax saturates and gradients vanish. Divide by sqrt(d_k), never by d_k.
- Softmax is row-wise over the keys and must be numerically stable: subtract each row's maximum before exponentiating.
Your Task
Implement:
def cross_attention(Q, K, V):
Return the (n_q, d_v) output as a nested list with every entry rounded to 4 decimals.
Input Format
- Q - nested list, shape (n_q, d_k)
- K - nested list, shape (n_kv, d_k)
- V - nested list, shape (n_kv, d_v)
Output Format
A nested list of n_q rows of d_v floats, each rounded to 4 decimals.
Sample
Q = [[1.0, 0.0]]
K = [[1.0, 0.0], [0.0, 1.0]]
V = [[1.0, 2.0], [3.0, 4.0]]
print(cross_attention(Q, K, V))
Output:
[[1.6605, 2.6605]]
Example
Q = [[1.0, 0.0]] K = [[1.0, 0.0], [0.0, 1.0]] V = [[1.0, 2.0], [3.0, 4.0]] print(cross_attention(Q, K, V))
[[1.6605, 2.6605]]
Scores are [1, 0] divided by sqrt(2) = [0.7071, 0]. Softmax gives weights [0.6698, 0.3302], so the mix of the two value rows is 0.6698*[1,2] + 0.3302*[3,4] = [1.6605, 2.6605]. Skipping the sqrt(2) scale would give weights [0.7311, 0.2689] and the different answer [1.5379, 2.5379].
Constraints
1 <= n_q, n_kv <= 64,1 <= d_k, d_v <= 64- Scale scores by
1 / sqrt(d_k)whered_kis the QUERY/KEY width, notd_v - Softmax runs over the key axis, one row per query, computed stably
- The output has one row per QUERY
- Round every entry to 4 decimals and avoid emitting
-0.0