Skip to content

English · Español

01 — Codificación posicional sinusoidal (sinusoidal positional encoding)

🇪🇸 Vaswani et al. (2017) usaron una codificación posicional fija basada en senos y cosenos a frecuencias geométricamente espaciadas. La idea: cada dimensión del embedding corresponde a una frecuencia distinta, y por tanto la PE de la posición \(p+\Delta\) es una rotación lineal de la PE de la posición \(p\). Esto le permite al modelo aprender a usar posiciones relativas sin que se las den explícitamente.

Este archivo deriva la fórmula sinusoidal de PE, explica por qué cada pieza (emparejamiento sin/cos, frecuencias geométricas, base \(10000\)) se eligió, y muestra la propiedad de cambio lineal que la hace funcionar.


La fórmula

Para la posición \(p \in \{0, 1, \ldots, T-1\}\) y el índice de dimensión \(i \in \{0, 1, \ldots, d-1\}\):

\[ \text{PE}(p, 2k) = \sin(p \cdot \omega_k), \qquad \text{PE}(p, 2k+1) = \cos(p \cdot \omega_k) \]

donde \(\omega_k = \frac{1}{10000^{2k/d}}\) para \(k = 0, 1, \ldots, d/2 - 1\).

El resultado es una matriz \(T \times d\). El embedding del token se convierte en:

\[ \tilde{x}_p = E[t_p] + \text{PE}(p) \]

Tres decisiones de diseño, cada una justificada abajo: emparejamiento sin/cos, espaciado geométrico de frecuencias, base \(10000\).

Decisión de diseño 1 — emparejamiento sin/cos

Las dimensiones están emparejadas: \((2k, 2k+1)\) forman juntas un dúo seno-coseno a la misma frecuencia \(\omega_k\).

¿Por qué? La propiedad de cambio lineal. Dada \(\text{PE}(p)\), ¿cuál es \(\text{PE}(p + \Delta p)\)? Usando la suma de ángulos:

\[ \sin((p + \Delta p) \omega_k) = \sin(p \omega_k) \cos(\Delta p \omega_k) + \cos(p \omega_k) \sin(\Delta p \omega_k) \]
\[ \cos((p + \Delta p) \omega_k) = \cos(p \omega_k) \cos(\Delta p \omega_k) - \sin(p \omega_k) \sin(\Delta p \omega_k) \]

En forma matricial, para el par de dimensiones \((2k, 2k+1)\):

\[ \begin{pmatrix} \text{PE}(p + \Delta p, 2k) \\ \text{PE}(p + \Delta p, 2k+1) \end{pmatrix} = \begin{pmatrix} \cos(\Delta p \omega_k) & \sin(\Delta p \omega_k) \\ -\sin(\Delta p \omega_k) & \cos(\Delta p \omega_k) \end{pmatrix} \begin{pmatrix} \text{PE}(p, 2k) \\ \text{PE}(p, 2k+1) \end{pmatrix} \]

Así que \(\text{PE}(p + \Delta p)\) es una función lineal de \(\text{PE}(p)\), con la matriz de rotación dependiendo solo de \(\Delta p\) (no de \(p\) en sí).

Consecuencia: el modelo puede, en principio, aprender una proyección lineal que extraiga "desplazamiento por \(\Delta\)" de cualquier codificación posicional. La posición relativa queda codificada.

Sin el emparejamiento sin/cos — por ejemplo, usando solo senos — perderías esta propiedad (un seno por sí solo no se transforma linealmente bajo desplazamiento; necesitas sin y cos para abarcar la rotación 2D).

🇪🇸 Punto clave: la pareja sin/cos por dimensión es lo que hace que "posición \(p+\Delta\)" se exprese como una rotación lineal de "posición \(p\)". El modelo puede aprender a usar esa rotación para razonar sobre posiciones relativas.

Decisión de diseño 2 — espaciado geométrico de frecuencias

Las frecuencias son \(\omega_k = 1 / 10000^{2k/d}\). Así que \(\omega_0 = 1, \omega_{d/2 - 1} \approx 1/10000\). Las frecuencias abarcan cuatro órdenes de magnitud geométricamente a lo largo de las dimensiones.

Dos razones:

¿Por qué geométrico y no aritmético?

El espaciado aritmético (\(\omega_k = k \cdot \text{paso}\)) da un rango dinámico pequeño. Con \(d = 64\), un paso aritmético de \(1/64\) da \(\omega\) en \(\{1/64, 2/64, \ldots, 1\}\). Dos posiciones separadas por un paso y dos posiciones muy separadas ven cambios de fase muy similares en cada frecuencia.

El espaciado geométrico cubre muchas escalas. A la frecuencia más alta (\(\omega_0 = 1\)), un cambio de posición de 1 causa un cambio de fase de 1 radián — muy sensible a la posición local. A la más baja (\(\omega_{d/2-1} \approx 1/10000\)), un cambio de posición de 1 causa un cambio de fase de 1e-4 — codifica información de muy largo alcance.

Esta propiedad multiescala es lo que permite que una sola PE represente tanto "el siguiente token" como "aproximadamente 1000 tokens atrás" en el mismo vector.

Conexión con la base de Fourier

Esto no es accidental: el conjunto geométrico de sinusoidales a múltiples frecuencias es una base de Fourier (truncada). La posición del token se expresa como un vector de coeficientes de Fourier de un impulso en \(p\). El modelo puede extraer cualquier banda de frecuencias mediante proyección lineal — es decir, puede extraer cualquier característica a una escala espacial.

Si has trabajado con procesado de imagen, esta es la misma lógica que las wavelets / descomposiciones de Fourier multirresolución: información a diferentes escalas codificada en diferentes componentes de la base.

Decisión de diseño 3 — base \(10000\)

¿Por qué \(10000\) específicamente?

La elección fija el rango de longitudes de onda. La longitud de onda más larga (en \(k = d/2 - 1\)) es \(2\pi \cdot 10000 \approx 63000\). La más corta es \(2\pi \approx 6.3\).

Para secuencias de longitud \(T \leq 10000\), no hay dos posiciones distintas con la misma PE (informalmente — la longitud de onda más larga puede distinguir posiciones separadas hasta ~10000).

La elección es algo arbitraria — existen artículos que usan base 5000 o base 50000. Para nuestro Mini-GPT con \(T = 128\), la base \(10000\) es excesiva (no necesitamos distinguir las posiciones 5000 y 9999), pero no hace daño.

Si realmente quisieras "ajustar" la base, podrías fijarla de modo que el rango de longitud de onda coincida con tu longitud máxima de secuencia: \(\theta_\text{base} \approx T_\text{max} / (2\pi)\). No te molestes en la Fase 17.

La propiedad de decaimiento-del-producto-escalar-con-distancia

Una propiedad sutil pero importante: el producto escalar \(\text{PE}(0) \cdot \text{PE}(p)\) decae a medida que \(p\) aumenta (con oscilación). Esto significa que los embeddings de posiciones cercanas se parecen más que los embeddings de posiciones lejanas — el sesgo inductivo natural de "cercano = relacionado".

Esbozo del por qué: \(\text{PE}(0) \cdot \text{PE}(p) = \sum_k [\sin(0) \sin(p \omega_k) + \cos(0) \cos(p \omega_k)] = \sum_k \cos(p \omega_k)\). La suma de cosenos a \(d/2\) frecuencias diferentes, cada una oscilando con periodos distintos. Para \(p\) grande, los cosenos se descorrelacionan (están en todas las fases), y la suma se acerca al promedio, que es aproximadamente \(0\) para \(d\) grande. Para \(p\) pequeño, todos los cosenos están cerca de \(1\), así que la suma es grande (cerca de \(d/2\)).

El decaimiento no es monótono — hay oscilaciones a medida que diferentes frecuencias entran en fase. El lab 01 lo plotea.

🇪🇸 Conclusión sobre PE sinusoidal: es una decisión de diseño defendible — multi-escala, paramless, y con la propiedad de cambio-lineal-bajo-traslación que permite al modelo aprender atención por posición relativa. No es la mejor opción para extrapolación (eso es RoPE), pero es históricamente el primer intento serio y sigue siendo razonable.

Ejemplo trabajado: \(T = 4, d = 8\)

Frecuencias: \(\omega_0 = 1, \omega_1 = 1/10, \omega_2 = 1/100, \omega_3 = 1/1000\). (En \(d = 8\): \(\omega_k = 1/10000^{2k/8} = 1/10000^{k/4}\), dando \(1, 10^{-1}, 10^{-2}, 10^{-3}\). Valores aproximados; los valores exactos vienen de \(\omega_k = 1/10^{k}\) aquí.)

\(p\) sin(\(p\omega_0\)) cos(\(p\omega_0\)) sin(\(p\omega_1\)) cos(\(p\omega_1\)) sin(\(p\omega_2\)) cos(\(p\omega_2\)) sin(\(p\omega_3\)) cos(\(p\omega_3\))
0 0.00 1.00 0.00 1.00 0.00 1.00 0.00 1.00
1 0.84 0.54 0.10 1.00 0.01 1.00 0.00 1.00
2 0.91 -0.42 0.20 0.98 0.02 1.00 0.00 1.00
3 0.14 -0.99 0.30 0.96 0.03 1.00 0.00 1.00

Las dimensiones de alta frecuencia (izquierda) oscilan rápidamente. Las dimensiones de baja frecuencia (derecha) apenas cambian sobre \(p = 0..3\). Cada fila es el vector PE de 8 dimensiones para esa posición.

El lab 01 te hace generar esto para \(T = 64, d = 32\) y visualizarlo como un heatmap.

Implementación (fijada para src/minimodel/positional/sinusoidal.py)

def sinusoidal_pe(T: int, d: int) -> np.ndarray:
    """Sinusoidal positional encoding, shape (T, d).

    Following Vaswani et al. 2017.
    """
    assert d % 2 == 0, "d must be even for sin/cos pairing"
    positions = np.arange(T, dtype=np.float32)[:, None]    # (T, 1)
    k = np.arange(d // 2, dtype=np.float32)                # (d/2,)
    omega = 1.0 / (10000.0 ** (2 * k / d))                 # (d/2,)
    angles = positions * omega                              # (T, d/2)
    pe = np.zeros((T, d), dtype=np.float32)
    pe[:, 0::2] = np.sin(angles)
    pe[:, 1::2] = np.cos(angles)
    return pe

Seis líneas. Borja escribe esto en el lab 01.

Lo que este archivo NO cubre

  • PE aprendida. Siguiente archivo (02-learned-vs-sinusoidal.md).
  • Rotary (RoPE). 03-rope.md.
  • ALiBi, sesgo T5. Mencionados de pasada en el archivo 02; no derivados.
  • Concatenación vs suma. Sumamos. La concatenación reserva dimensiones; la suma las comparte. La elección es "convención" — la derivación pertenece a una tesis, no aquí.
  • Ajustar la base \(\theta = 10000\). La Fase 17 la deja en el valor por defecto. Ajustarla es tema de investigación.

Resumen

  • PE sinusoidal: \((2k, 2k+1)\) emparejados, sin/cos a la frecuencia \(\omega_k = 1 / 10000^{2k/d}\).
  • Tres decisiones de diseño: par sin/cos (propiedad de cambio lineal), frecuencias geométricas (multiescala), base \(10000\) (rango de longitud de onda).
  • Cambio lineal: \(\text{PE}(p + \Delta)\) es una rotación fija de \(\text{PE}(p)\), lo que permite razonamiento implícito sobre posición relativa.
  • Frecuencias multiescala = base de Fourier (truncada) en la posición.
  • Decaimiento del producto escalar con la distancia: las posiciones cercanas se ven más similares.

Siguiente: 02-learned-vs-sinusoidal.md.