Skip to content

English · Español

Lab 02 — Implementación de RoPE y la propiedad de posición relativa

Objetivo: implementar RoPE en NumPy; verificar numéricamente que \(\langle R_m q, R_n k \rangle = \langle q, R_{n-m} k \rangle\) con precisión 1e-5.

Tiempo estimado: 90–120 minutos.

Prerrequisito: lab 01 commiteado; theory/03-rope.md leído.


Lo que produces

Un directorio experiments/16-rope/ que contiene:

  • rope.py — tu implementación, en src/minimodel/positional/rope.py.
  • verify.py — script de verificación.
  • verify_output.txt — printout capturado.
  • relative_position_demo.png — gráfico que muestra que la propiedad de posición relativa se cumple a lo largo de \(m - n\).
  • manifest.json.
  • README.md.

TODOs

Bloque A — implementación

Según src/minimodel/positional/BLUEPRINT.md:

  • rope_frequencies(d_head: int, base: float = 10000.0) -> np.ndarray. Forma (d_head/2,).
  • precompute_rope(T: int, d_head: int) -> tuple[np.ndarray, np.ndarray]. Devuelve (cos_pe, sin_pe) de forma (T, d_head/2).
  • apply_rope(q, k, cos_pe, sin_pe) -> tuple[np.ndarray, np.ndarray]. Cada uno de forma (T, d_head).

Implementa usando la convención de "pares entrelazados" (ver theory/03-rope.md): par \((2k, 2k+1)\) rotado en conjunto.

def apply_rope(q, k, cos_pe, sin_pe):
    # q, k: (T, d_head)
    # cos_pe, sin_pe: (T, d_head // 2)
    q_even = q[:, 0::2]
    q_odd  = q[:, 1::2]
    q_rope_even = q_even * cos_pe - q_odd * sin_pe
    q_rope_odd  = q_even * sin_pe + q_odd * cos_pe
    q_rope = np.empty_like(q)
    q_rope[:, 0::2] = q_rope_even
    q_rope[:, 1::2] = q_rope_odd
    # same for k
    ...
    return q_rope, k_rope

Unas 15 LOC en total para apply_rope. Hecho a mano; sin atajos.

Bloque B — verifica la propiedad de posición relativa

Este es el test clave. La propiedad: \(\langle R_m q, R_n k \rangle = \langle q, R_{n-m} k \rangle\) para cualquier \(q, k, m, n\).

  • Elige d_head = 16, \(T = 32\).
  • Genera q, k aleatorios de forma (d_head,) con semilla 0.
  • Precalcula cos_pe, sin_pe = precompute_rope(T=32, d_head=16).
  • Para cada par \((m, n) \in \{(0, 1), (3, 7), (5, 5), (10, 12), (20, 8)\}\):
  • Calcula q_m = apply_rope_single(q, cos_pe[m], sin_pe[m]) y k_n = apply_rope_single(k, cos_pe[n], sin_pe[n]).
  • Calcula lhs = q_m @ k_n.
  • Calcula k_diff = apply_rope_single(k, cos_pe[n - m], sin_pe[n - m]) si \(n \geq m\), si no usa cos_pe[-(m-n)] con el signo de \(\sin\) invertido.
  • Calcula rhs = q @ k_diff.
  • Asegura abs(lhs - rhs) < 1e-5.
  • Imprime los cinco pares en una tabla.

(apply_rope_single es una versión de una sola posición de apply_rope por conveniencia. O implementa el apply_rope completo y haz slicing.)

Bloque C — plotea la propiedad de posición relativa

  • Para \(m = 0\) fijo, varía \(n\) de 0 a 31.
  • En cada \(n\), calcula \(\langle R_m q, R_n k \rangle\).
  • Plotea como función de \(n - m\).
  • Superpón \(\langle q, R_{n-m} k \rangle\) sobre los mismos ejes.
  • Las dos curvas tienen que coincidir exactamente (con tolerancia 1e-5).
  • Guarda como relative_position_demo.png.

Bloque D — sanity: RoPE preserva la norma

Las rotaciones son ortogonales: \(\|R q\| = \|q\|\).

  • q aleatorio de forma (d_head,).
  • Para cada posición \(p \in \{0, 1, 5, 100\}\), calcula la versión rotada.
  • Asegura np.allclose(np.linalg.norm(q_rotated), np.linalg.norm(q), atol=1e-6).

Bloque E — redacción

En README.md:

  1. Enuncia la propiedad de posición relativa. En una frase.
  2. Reporta la diferencia máxima a través de los cinco casos del Bloque B. Debería ser < 1e-5.
  3. Confirma la preservación de la norma (Bloque D).
  4. ¿Por qué V no se rota? Desde theory/03-rope.md, con tus propias palabras.

Bloque F — manifest

{
  "experiment": "16-rope",
  "date": "YYYY-MM-DD",
  "seed": 0,
  "versions": { "python": "3.11.x", "numpy": "X.Y.Z" },
  "config": {
    "d_head": 16,
    "T": 32,
    "rope_base": 10000.0
  },
  "results_summary": {
    "relative_position_max_diff": null,
    "norm_preservation_max_diff": null
  }
}

Restricciones

  • Sin PyTorch.
  • Convención de pares entrelazados. Documéntalo claramente en README.md. Otras implementaciones usan la convención de "split-half"; esa también es válida pero las dos no son bit-equivalentes.
  • Sin einsums sofisticados. Usa indexación explícita y concatenación. Vectorizado pero legible.

Condiciones de parada

Hecho cuando:

  1. Los seis archivos commiteados.
  2. Las aserciones del Bloque B pasan todas (diferencia máxima < 1e-5).
  3. La preservación de la norma del Bloque D pasa.
  4. relative_position_demo.png muestra dos curvas coincidentes.
  5. README.md contesta a las cuatro preguntas del Bloque E.

Escollos

  • Convención de signos. La matriz de rotación en theory/03-rope.md usa \(\begin{pmatrix} \cos & -\sin \\ \sin & \cos \end{pmatrix}\). Esto es "antihorario". Algunas referencias usan horario (signos invertidos). Atente a la convención del archivo de teoría.
  • apply_rope_single(q, cos_pe[m], sin_pe[m]). Cuando m > n, la posición relativa es negativa. La rotación por \(-\theta\) tiene \(\sin(-\theta) = -\sin(\theta)\). Maneja el signo correctamente al calcular el rhs.
  • cos_pe[n - m] requiere \(n - m \geq 0\). Para los casos de test con \(n < m\), calcula la rotación a mano en lugar de indexar.
  • Precisión float. Con float32 y posiciones grandes, los valores trigonométricos pierden precisión. Para los T pequeños de la Fase 16 esto está bien; márcalo para Fase 22+ si Borja se extiende a contextos largos.

Cuándo consultar solutions/

Tras commitear los seis archivos y que las aserciones pasen. Solución en solutions/02-rope-ref.md.


Siguiente lab: 03-extrapolation-compare.md.