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:
Attention(Q,K,V)=softmax(dkββQKβ€β)V
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
1. Background Knowledge
Cross-attention is a fundamental mechanism in Vision-Language Models (VLMs) that allows one sequence of tokens (queries) to attend to information from a different sequence (keys and values). Unlike self-attention, where queries, keys, and values are derived from the same input, cross-attention decouples these roles. In VLMs, the queries typically come from the language model or a learned set of resampler tokens, while the keys and values originate from the vision encoderβs patch features. This enables the language side to selectively "read" relevant visual information.
The core computation is defined as:
Attention(Q,K,V)=softmax(dkββQKβ€β)VHere, QβRnqβΓdkβ, KβRnkvβΓdkβ, and VβRnkvβΓdvβ. The output shape is (nqβ,dvβ), meaning the number of output tokens matches the number of queries, not the keys. This is crucial for tasks like image captioning, where a fixed number of text tokens must aggregate information from a variable number of image patches.
Two critical numerical considerations arise:
- Scaling by dkββ: Without this scaling, dot products between high-dimensional vectors tend to have large magnitudes, causing the softmax function to saturate (outputs approach 0 or 1). This leads to vanishing gradients during training. Dividing by dkββ keeps the variance of the dot products stable.
- Numerical Stability of Softmax: Directly computing exp(x) can overflow for large x. The standard trick is to subtract the maximum value in each row before exponentiating: softmax(xiβ)=βjβexp(xjββmax(x))exp(xiββmax(x))β. This does not change the result but prevents overflow.
2. Algorithm Approach
The problem requires implementing the scaled dot-product cross-attention from scratch using nested lists. The approach involves three main phases:
- Matrix Multiplication (QKβ€): Compute the dot product between each query vector and each key vector. This results in an attention score matrix of shape (nqβ,nkvβ).
- Scaling and Softmax: Scale the scores by 1/dkββ and apply a numerically stable softmax row-wise to obtain attention weights. These weights sum to 1 for each query.
- Weighted Sum (AV): Multiply the attention weight matrix by the value matrix V to produce the final output. Each output token is a weighted sum of the value vectors, where the weights are determined by the attention scores.
Since the input is given as nested lists, you must implement matrix operations manually or use helper functions for dot products and matrix multiplication. Avoid using external libraries like NumPy unless explicitly allowed; the problem likely tests your understanding of the underlying mechanics.
3. Step-by-Step Strategy
- Extract Dimensions: Determine nqβ (number of queries), nkvβ (number of keys/values), dkβ (dimension of keys/queries), and dvβ (dimension of values) from the input lists.
- Compute Attention Scores:
- Initialize a matrix scores of shape (nqβ,nkvβ).
- For each query i and key j, compute the dot product Q[i]β K[j].
- Store the result in scores[i][j].
- Scale the Scores:
- Calculate the scaling factor s=1/dkββ.
- Multiply every element in scores by s.
- Apply Numerically Stable Softmax:
- For each row i in scores:
- Find the maximum value miβ=maxjβ(scores[i][j]).
- Subtract miβ from each element in the row.
- Exponentiate each element: exp(scores[i][j]βmiβ).
- Sum the exponentiated values to get the denominator Siβ.
- Divide each exponentiated element by Siβ to get the attention weights A[i][j].
- Compute Output:
- Initialize an output matrix out of shape (nqβ,dvβ).
- For each query i and value dimension k:
- Compute the weighted sum: out[i][k]=βj=0nkvββ1βA[i][j]β V[j][k].
- Round and Return:
- Round each element in out to 4 decimal places.
- Return the nested list.
4. Common Pitfalls
- Incorrect Scaling: Dividing by dkβ instead of dkββ will cause the attention scores to be too small, leading to nearly uniform attention weights and poor performance. Always use dkββ.
- Softmax Overflow: Failing to subtract the row maximum before exponentiating can lead to inf values if the scores are large. Always implement the stable softmax variant.
- Shape Mismatch: Ensure that the output shape is (nqβ,dvβ), not (nkvβ,dvβ). The number of output tokens is determined by the number of queries.
- Manual Matrix Multiplication Errors: When implementing dot products manually, ensure you are iterating over the correct dimensions. For QKβ€, you are dotting rows of Q with rows of K. For AV, you are dotting rows of A with columns of V.
- Rounding Precision: Round only at the final step. Intermediate rounding can accumulate errors and lead to incorrect final results.
5. Time & Space Complexity
-
Time Complexity:
-
Computing QKβ€: O(nqββ nkvββ dkβ).
-
Softmax: O(nqββ nkvβ).
-
Computing AV: O(nqββ nkvββ dvβ).
-
Overall: O(nqββ nkvββ (dkβ+dvβ)). This is dominated by the matrix multiplications.
-
Space Complexity:
-
Attention Scores Matrix: O(nqββ nkvβ).
-
Attention Weights Matrix: O(nqββ nkvβ).
-
Output Matrix: O(nqββ dvβ).
-
Overall: O(nqββ nkvβ+nqββ dvβ). If we reuse the scores matrix for weights, it can be reduced to O(nqββ nkvβ).