Exercise 2: Mapping Scaled Dot-Product Attention Back to nlp1-8's Conceptual Version — Possible Solution ==================================================================== nlp1-8's OWN CONCEPTUAL VERSION, RESTATED ------------------------------ Per this chapter's own recap, nlp1-8's mechanism was: compute a similarity score between a decoder state and each encoder state (via a dot product), pass those scores through softmax to get normalized weights, then blend the encoder states together using those weights to produce one context vector. MAPPING "Q @ K.T" TO nlp1-8's OWN SCORING STEP ------------------------------ nlp1-8 computed `dot(decoder_state, encoder_state_i)` for each encoder state individually, one score at a time, in a loop. This chapter's own `Q @ K.T` computes the identical kind of dot-product similarity, but for every Query against every Key simultaneously, in a single matrix multiplication — the same underlying operation (a dot product measuring similarity), generalized from "one query against many keys" to "many queries against many keys, all at once." MAPPING THE "/ sqrt(d_k)" SCALING STEP ------------------------------ This step has no direct counterpart in nlp1-8's own simpler version — it's a refinement specific to this chapter's own formalized mechanism, addressing a numerical-stability concern (worked through in Exercise 3) that only becomes significant once real, higher-dimensional vectors are involved. MAPPING "softmax" TO nlp1-8's OWN WEIGHT STEP ------------------------------ nlp1-8's own `weights = softmax(scores)` step converted the raw dot-product scores into a normalized probability distribution summing to 1. This chapter's own `weights = softmax(scores, axis=-1)` does the identical thing, applied per-row so that each individual token gets its own separate probability distribution over every other token, rather than one single distribution shared across an entire sequence. MAPPING "weights @ V" TO nlp1-8's OWN CONTEXT-BLENDING STEP ------------------------------ nlp1-8's own `context = sum(w * s for w, s in zip(weights, encoder_states))` blended the encoder states together using the computed weights to produce one context vector. This chapter's own `output = weights @ V` does the same weighted blend, but of Value vectors rather than raw encoder states directly, and produces one output vector per token rather than a single context vector for one decoder step. WHY THIS MAPPING SHOWS FORMALIZATION RATHER THAN REPLACEMENT ------------------------------ Every step in this chapter's own scaled dot-product attention formula corresponds to a step nlp1-8 already introduced conceptually — nothing new in kind has been added except the sqrt(d_k) scaling term. What changed is that a single decoder-to-encoder comparison, computed one score at a time, has been generalized into every-token-to-every-token comparison, computed all at once via matrix operations, and applied to learned Q/K/V projections rather than raw hidden states directly. WHY THIS WORKS AS AN ANSWER ------------------------------ It walks through each piece of this chapter's own formula in order and identifies the specific corresponding step in nlp1-8's own conceptual version, showing that the formalized mechanism is a generalization of the earlier one rather than an unrelated new technique.