{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Qué está pasando cuando le escribes a un modelo de lenguaje\n",
    "\n",
    "Tokens, el bloque completo, el bucle que escribe palabra a palabra y qué hace de verdad la temperatura.\n",
    "\n",
    "Cuaderno de práctica del capítulo 17 de **Deep learning desde cero**, de Miss Yera.\n",
    "\n",
    "Corre de arriba abajo. Si lo abres en Google Colab no necesitas instalar nada.\n",
    "\n",
    "Capítulo completo: https://missyera.com/guias/deep-learning-desde-cero/del-transformer-al-llm/\n",
    "\n",
    "Los ejercicios están al final y traen una celda vacía debajo de cada uno. Las\n",
    "respuestas viven en el cuaderno de soluciones, y merece la pena pelearse un\n",
    "rato antes de abrirlo 💛"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Antes de empezar\n",
    "\n",
    "Esta celda baja el ayudante que corrige tus ejercicios. Después, en cada\n",
    "ejercicio que se pueda corregir solo, vas a ver `%%revisa` arriba de la celda:\n",
    "escribe tu respuesta debajo, ejecuta, y te digo si te salió 💛"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import urllib.request\n",
    "\n",
    "# El ayudante de los cuadernos. Trae la corrección de los ejercicios y, en los\n",
    "# capítulos de consola, la celda mágica que ejecuta los comandos. Se baja en\n",
    "# vez de venir pegado aquí para que siempre sea el último.\n",
    "urllib.request.urlretrieve(\n",
    "    \"https://missyera.com/static/cuadernos/revisa.py\", \"revisa.py\")\n",
    "import revisa\n",
    "revisa.carga({\n",
    "    1: \"bGltYSAgICAgICAgICA0IGxldHJhcyAtPiAyIHRyb3pvcyAgWydsaScsICdtYSddCmJvZGVnYSAgICAgICAgNiBsZXRyYXMgLT4gMyB0cm96b3MgIFsnYm8nLCAnZGUnLCAnZ2EnXQptaW5pbWFya2V0ICAgMTAgbGV0cmFzIC0+IDQgdHJvem9zICBbJ21pbicsICdpJywgJ21hcicsICdrZXQnXQpodWFuY2F5byAgICAgIDggbGV0cmFzIC0+IDQgdHJvem9zICBbJ2h1JywgJ2FuJywgJ2NhJywgJ3lvJ10=\",\n",
    "    2: \"Wyc/JywgJ2EnLCAnPycsICdhJywgJz8nLCAnPycsICdvJ10KWyc/JywgJz8nLCAnPydd\",\n",
    "    3: \"VD0wLjI6CiAgICBlbCBwZWRpZG8gZGUgYWNlaXRlCiAgICBlbCBwZWRpZG8gbGxlZ28gY29tcGxldG8geSBhenVjYXIKICAgIGVsIG1pbmltYXJrZXQgZWwgcGVkaWRvIGRlIGFycm96IHkgYXp1Y2FyClQ9MS4wOgogICAgZWwgbWluaW1hcmtldCBlbCBhY2VpdGUKICAgIGVsIG1pbmltYXJrZXQgcmVjbGFtbwogICAgZWwgbWFydGVzClQ9My4wOgogICAgZWwgbWluaW1hcmtldCBlbCBhY2VpdGUKICAgIGVsIG1heW9yaXN0YSByZWNsYW1vCiAgICBlbCBsdW5lcw==\",\n",
    "    4: \"bGEgYm9kZWdhIGVsIHBlZGlkbyBkZWwgbWluaW1hcmtldCBsbGVnbyBpbmNvbXBsZXRvCmxhIGJvZGVnYSBlbCBwZWRpZG8gZGVsIG1pbmltYXJrZXQgbGxlZ28gaW5jb21wbGV0bwrCv2lkw6ludGljYXM/IFRydWU=\",\n",
    "    5: \"ZD0gICAgODogYXRlbmNpw7NuICAgICAgICAgIDI1NiAgZmVlZGZvcndhcmQgICAgICAgICAgNTEyICAoNjclIGVuIGVsIGZlZWRmb3J3YXJkKQpkPSAgNzY4OiBhdGVuY2nDs24gICAgMiwzNTksMjk2ICBmZWVkZm9yd2FyZCAgICA0LDcxOCw1OTIgICg2NyUgZW4gZWwgZmVlZGZvcndhcmQpCmQ9IDQwOTY6IGF0ZW5jacOzbiAgIDY3LDEwOCw4NjQgIGZlZWRmb3J3YXJkICAxMzQsMjE3LDcyOCAgKDY3JSBlbiBlbCBmZWVkZm9yd2FyZCk=\",\n",
    "    6: \"YW50ZXMsIG1lZGlhcyAgICAgIDogWzEuMDAwNWUrMDMgMS40MDAwZS0wM10KYW50ZXMsIGRlc3ZpYWNpb25lczogWzEuMTE4ZSswMCA0LjAwMGUtMDRdCmRlc3B1w6lzLCBtZWRpYXMgICAgICA6IFsgMC4gLTAuXQpkZXNwdcOpcywgZGVzdmlhY2lvbmVzOiBbMS4gICAgIDAuMTIzNV0=\",\n",
    "    7: \"S2V5RXJyb3I6ICdob2xhJw==\",\n",
    "}, lenguaje=\"python\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ya tenemos todas las piezas. Aquí las juntamos y respondemos la pregunta con\n",
    "la que mucha gente llega a este libro: **qué está pasando exactamente\n",
    "cuando le escribes a ChatGPT o a Claude** 🤖\n",
    "\n",
    "Y antes de contestarla, dime tú: **¿te has imaginado alguna vez que el modelo \"entiende\" la pregunta y luego busca la respuesta?** Casi todo el mundo se lo imagina así, y no es lo que pasa 💬"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Primero: el texto se parte en trozos"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En el capítulo 13 quedó un error abierto: pedir el vector de \"huancayo\"\n",
    "reventaba porque esa palabra no estaba en el vocabulario. Así se arregla:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "CONOCIDOS = {'bo', 'de', 'ga', 'hu', 'an', 'ca', 'yo', 'li', 'ma', 'min',\n",
    "             'i', 'mar', 'ket', 'a', 'o', 'e', 'u', 'n', 'r', 's', 't'}\n",
    "\n",
    "def trozos(palabra, conocidos):\n",
    "    salida, i = [], 0\n",
    "    while i < len(palabra):\n",
    "        for j in range(len(palabra), i, -1):        # el trozo más largo que quepa\n",
    "            if palabra[i:j] in conocidos:\n",
    "                salida.append(palabra[i:j])\n",
    "                i = j\n",
    "                break\n",
    "        else:\n",
    "            salida.append('?')\n",
    "            i += 1\n",
    "    return salida\n",
    "\n",
    "for w in ['bodega', 'huancayo', 'minimarket', 'lima']:\n",
    "    print(f'{w:12} -> {trozos(w, CONOCIDOS)}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí está: \"huancayo\" se parte en hu-an-ca-yo. **No hace falta que la\n",
    "palabra esté en el vocabulario, basta con que estén sus trozos** 🧩\n",
    "\n",
    "Eso es la tokenización por subpalabras, y es por lo que un modelo puede leer\n",
    "un nombre propio peruano que nunca vio, o una palabra inventada, o un error de\n",
    "tipeo.\n",
    "\n",
    "También explica dos cosas que se notan usándolos. Que el español gaste más\n",
    "tokens que el inglés (los vocabularios se arman mirando sobre todo texto en\n",
    "inglés, así que nuestras palabras se parten en más trozos), y que a los modelos\n",
    "les cueste contar letras: ellos no ven letras, ven trozos."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Segundo: el bloque completo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "a^=γa−μσ2+ε+β\n",
    "\n",
    "recentra y reescala las activaciones de cada ejemplo, y ese epsilon de abajo es lo que evita dividir entre cero cuando la varianza es minúscula"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La atención del capítulo 16 no va sola. Un bloque de transformer le añade tres\n",
    "cosas, y las tres son necesarias:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def softmax(z):\n",
    "    z = z - z.max(axis=-1, keepdims=True)\n",
    "    e = np.exp(z)\n",
    "    return e / e.sum(axis=-1, keepdims=True)\n",
    "\n",
    "def norma(X, eps=1e-5):\n",
    "    return (X - X.mean(-1, keepdims=True)) / np.sqrt(X.var(-1, keepdims=True) + eps)\n",
    "\n",
    "rng = np.random.default_rng(7)\n",
    "n, d = 6, 8\n",
    "X = rng.normal(0, 1, (n, d))\n",
    "Wq, Wk, Wv = [rng.normal(0, 0.5, (d, d)) for _ in range(3)]\n",
    "W1 = rng.normal(0, 0.5, (d, 4 * d))\n",
    "W2 = rng.normal(0, 0.5, (4 * d, d))\n",
    "\n",
    "def bloque(X):\n",
    "    A = softmax((X @ Wq) @ (X @ Wk).T / np.sqrt(d))\n",
    "    X = norma(X + A @ (X @ Wv))                       # atención + residual + norma\n",
    "    X = norma(X + np.maximum(0, X @ W1) @ W2)         # feedforward + residual + norma\n",
    "    return X\n",
    "\n",
    "Y = bloque(X)\n",
    "print('entra', X.shape, 'y sale', Y.shape)\n",
    "print('media de cada fila al salir     :', np.round(Y.mean(axis=1), 3))\n",
    "print('desviación de cada fila al salir:', np.round(Y.std(axis=1), 3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tres añadidos y cada uno resuelve un problema concreto 🔧\n",
    "\n",
    "- ➕ El **residual**, ese `X +`, deja pasar la entrada\n",
    "sin tocar al lado de lo que calculó la capa. Así el gradiente del capítulo 6\n",
    "tiene un camino directo hasta abajo sin multiplicarse por nada, y por eso se\n",
    "pueden apilar cien capas.\n",
    "\n",
    "- 📏 La **norma por capa** centra cada fila en media 0 y\n",
    "desviación 1. Mira la salida: todas las medias son 0 y todas las desviaciones 1,\n",
    "exacto.\n",
    "\n",
    "- 🧠 El **feedforward** es la red del capítulo 4 aplicada a cada\n",
    "posición por separado, y suele ser cuatro veces más ancha. Ahí está la mayoría de\n",
    "los pesos de un modelo de lenguaje, aunque la fama se la lleve la atención."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Por qué el residual no es opcional"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "𝐚(l+1)=𝐚(l)+F(𝐚(l))\n",
    "\n",
    "la capa aprende lo que hay que sumarle a la entrada en vez de la salida entera, y por eso el gradiente encuentra siempre un camino corto de vuelta"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def bloque_sin_residual(X):\n",
    "    A = softmax((X @ Wq) @ (X @ Wk).T / np.sqrt(d))\n",
    "    X = norma(A @ (X @ Wv))\n",
    "    X = norma(np.maximum(0, X @ W1) @ W2)\n",
    "    return X\n",
    "\n",
    "def parecido(a, b):\n",
    "    return float(a @ b / (np.linalg.norm(a) * np.linalg.norm(b)))\n",
    "\n",
    "Z = X.copy()\n",
    "for _ in range(6):\n",
    "    Z = bloque_sin_residual(Z)\n",
    "print('sin residual, tras 6 bloques, parecido entre dos posiciones:',\n",
    "      round(parecido(Z[0], Z[1]), 4))\n",
    "\n",
    "Z = X.copy()\n",
    "for capa in range(6):\n",
    "    Z = bloque(Z)\n",
    "    print(f'con residual, tras {capa + 1} bloques: magnitud media {np.abs(Z).mean():.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Sin residual, después de seis bloques las dos posiciones tienen un parecido de\n",
    "**1,0**: son el mismo vector. Todas las palabras se volvieron\n",
    "idénticas y el modelo perdió la frase entera.\n",
    "\n",
    "Con residual, la magnitud se queda entre 0,80 y 0,83 bloque tras bloque, sin\n",
    "crecer ni apagarse.\n",
    "\n",
    "Una nota honesta: aquí los pesos son aleatorios, así que ese colapso es más\n",
    "brutal de lo que sería con un modelo entrenado. Lo que la medición enseña de\n",
    "verdad es que **el residual y la norma son lo que mantiene estable una pila\n",
    "de bloques**, y sin ellos las redes profundas de texto no se pueden\n",
    "entrenar 🏗️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Tercero: el bucle que escribe"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y ahora la parte que sorprende a casi todo el mundo. Un modelo de lenguaje\n",
    "hace **una sola cosa**: dada una secuencia, poner una probabilidad a\n",
    "cada token posible como siguiente.\n",
    "\n",
    "Escribir es repetir eso: eliges uno, lo pegas al final, y vuelves a\n",
    "preguntar."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "COMENTARIOS = [\n",
    "    'el pedido llego completo y a tiempo', 'el pedido llego incompleto y con retraso',\n",
    "    'la bodega pidio arroz y azucar', 'el minimarket pidio aceite y azucar',\n",
    "    'el mayorista pidio arroz en cantidad', 'el pedido de la bodega llego tarde',\n",
    "    'el pedido del minimarket llego tarde', 'la bodega reclamo por el retraso del pedido',\n",
    "    'el minimarket reclamo por el aceite roto', 'el mayorista reclamo por la factura',\n",
    "    'llego todo completo sin reclamo', 'el arroz llego en buen estado',\n",
    "    'el aceite llego en buen estado', 'el azucar llego roto y con retraso',\n",
    "    'la bodega pago la factura a tiempo', 'el minimarket pago la factura con retraso',\n",
    "    'el mayorista pago la factura a tiempo', 'el vendedor visito la bodega el lunes',\n",
    "    'el vendedor visito el minimarket el martes', 'el vendedor visito al mayorista el lunes',\n",
    "    'la bodega compro arroz el lunes', 'el minimarket compro aceite el martes',\n",
    "    'el mayorista compro arroz y aceite', 'nadie visito la bodega esta semana',\n",
    "    'el pedido de arroz llego completo', 'el pedido de aceite llego incompleto',\n",
    "]\n",
    "\n",
    "vocabulario = sorted({p for f in COMENTARIOS for p in f.split()} | {'<fin>'})\n",
    "lugar = {p: i for i, p in enumerate(vocabulario)}\n",
    "V = len(vocabulario)\n",
    "\n",
    "siguiente = np.zeros((V, V))\n",
    "for f in COMENTARIOS:\n",
    "    ps = f.split() + ['<fin>']\n",
    "    for a, b in zip(ps, ps[1:]):\n",
    "        siguiente[lugar[a], lugar[b]] += 1\n",
    "\n",
    "def escribe(inicio, temperatura=1.0, semilla=0, maximo=10):\n",
    "    r = np.random.default_rng(semilla)\n",
    "    salida = [inicio]\n",
    "    for _ in range(maximo):\n",
    "        fila = siguiente[lugar[salida[-1]]]\n",
    "        if fila.sum() == 0:\n",
    "            break\n",
    "        puntajes = np.where(fila > 0, np.log(fila + 1e-9), -1e9) / temperatura\n",
    "        p = np.exp(puntajes - puntajes.max())\n",
    "        p = p / p.sum()\n",
    "        elegida = vocabulario[r.choice(V, p=p)]\n",
    "        if elegida == '<fin>':\n",
    "            break\n",
    "        salida.append(elegida)\n",
    "    return ' '.join(salida)\n",
    "\n",
    "for s in range(4):\n",
    "    print(escribe('el', semilla=s))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Eso es un modelo de lenguaje. Uno malísimo, porque solo mira la palabra\n",
    "anterior en vez de toda la frase con atención, y porque aprendió de 26\n",
    "comentarios en vez de billones de palabras.\n",
    "\n",
    "Pero **el bucle es exactamente el mismo**: mirar, poner\n",
    "probabilidades, elegir, pegar, repetir. Cuando ves a Claude escribir palabra a\n",
    "palabra, es esto, con un transformer en el medio en vez de una tabla 🔁\n",
    "\n",
    "Y de aquí sale algo que conviene entender: el modelo **no tiene un plan\n",
    "de lo que va a decir**. Cada token se elige con lo que hay escrito hasta\n",
    "ese momento. Que las respuestas salgan coherentes es consecuencia de que el\n",
    "contexto incluye todo lo que ya escribió."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La temperatura, que es lo único que ajustas tú"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "palabras = ['completo', 'tarde', 'roto', 'nunca', 'ya']\n",
    "puntajes = np.array([3.2, 2.1, 0.8, -0.5, -1.4])\n",
    "\n",
    "def con_temperatura(z, T):\n",
    "    z = z / T\n",
    "    e = np.exp(z - z.max())\n",
    "    return e / e.sum()\n",
    "\n",
    "print(f\"{'palabra':12}\" + ''.join(f'{f\"T={t}\":>10}' for t in [0.2, 0.5, 1.0, 1.5, 3.0]))\n",
    "for i, p in enumerate(palabras):\n",
    "    print(f'{p:12}' + ''.join(f'{con_temperatura(puntajes, t)[i]:10.4f}'\n",
    "                              for t in [0.2, 0.5, 1.0, 1.5, 3.0]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí está lo que hace, y es una sola división 🌡️\n",
    "\n",
    "Con T=0,2 la favorita se lleva **0,9959** y las demás quedan en\n",
    "nada: el modelo va a decir siempre lo mismo. Con T=3,0 la favorita baja a\n",
    "**0,3774** y hasta \"ya\", que era la última, se lleva 0,0815.\n",
    "\n",
    "Y fíjate en lo que la temperatura *no* hace: **no cambia el orden\n",
    "ni cambia lo que el modelo sabe**. Completo sigue siendo la más probable\n",
    "en las cinco columnas. Solo cambia cuánto se arriesga al elegir.\n",
    "\n",
    "Por eso \"bájale la temperatura para que no alucine\" funciona a medias: si el\n",
    "modelo tiene mal el dato, con T=0 lo va a decir mal con toda seguridad 😬"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Los otros dos filtros"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = con_temperatura(puntajes, 1.0)\n",
    "\n",
    "def top_k(p, k):\n",
    "    q = p.copy()\n",
    "    corte = np.sort(q)[::-1][k - 1]\n",
    "    q[q < corte] = 0\n",
    "    return q / q.sum()\n",
    "\n",
    "def top_p(p, umbral):\n",
    "    orden = np.argsort(p)[::-1]\n",
    "    acumulado = np.cumsum(p[orden])\n",
    "    cuantas = int(np.searchsorted(acumulado, umbral)) + 1\n",
    "    q = np.zeros_like(p)\n",
    "    q[orden[:cuantas]] = p[orden[:cuantas]]\n",
    "    return q / q.sum()\n",
    "\n",
    "print('sin filtro:', np.round(p, 4))\n",
    "print('top-k, k=2:', np.round(top_k(p, 2), 4))\n",
    "print('top-p, 0.9:', np.round(top_p(p, 0.9), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El **top-k** se queda con las k mejores y tira el resto. El\n",
    "**top-p** se queda con las que hagan falta para juntar ese\n",
    "porcentaje de probabilidad, así que el corte se mueve solo: si el modelo está\n",
    "segurísimo se queda con una, y si duda se queda con muchas.\n",
    "\n",
    "Aquí los dos dan lo mismo porque con estos cinco números las dos primeras ya\n",
    "suman el 91%. Con un vocabulario de 100.000 tokens la diferencia se nota\n",
    "muchísimo, y por eso el top-p es el que se usa hoy 🎚️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que este capítulo NO explica"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y esto es importante para que no salgas de aquí creyendo que ya sabes cómo se\n",
    "hace un ChatGPT.\n",
    "\n",
    "- 📚 **El entrenamiento.** Todo lo de este libro, pero sobre\n",
    "billones de palabras y durante meses, en miles de tarjetas gráficas. La\n",
    "retropropagación es la misma; la escala no.\n",
    "\n",
    "- 🎓 **El ajuste con personas.** Un modelo entrenado solo a\n",
    "predecir la palabra siguiente no contesta preguntas: completa texto. Que sea útil\n",
    "y educado sale de una fase posterior con evaluaciones humanas.\n",
    "\n",
    "- 🧯 **Las barreras.** Lo que decide qué no responde no está en la\n",
    "arquitectura.\n",
    "\n",
    "Lo que sí te llevas es la maquinaria: tokens, atención, bloques apilados y un\n",
    "bucle que elige. Y con eso ya puedes leer un paper y entender de qué habla 💛"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cómo se le baja el peso a un modelo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un modelo son sus pesos, y los pesos se guardan como números decimales de\n",
    "cuatro bytes cada uno. Con miles de millones de pesos, eso es lo que decide si\n",
    "entra en una tarjeta gráfica o no entra.\n",
    "\n",
    "El truco que se usa para bajarlo se llama **cuantización** y es\n",
    "más simple de lo que suena: guardar cada peso con menos precisión. En vez de\n",
    "cuatro bytes, uno, que da 256 valores posibles. Se busca la escala que estira\n",
    "esos 256 escalones sobre el rango real de los pesos, y se redondea:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pesos = np.random.default_rng(0).normal(0, 0.5, 4096).astype(np.float32)\n",
    "\n",
    "escala = np.abs(pesos).max() / 127\n",
    "q = np.round(pesos / escala).astype(np.int8)\n",
    "vuelta = q.astype(np.float32) * escala\n",
    "\n",
    "print('float32 :', pesos.nbytes, 'bytes')\n",
    "print('int8    :', q.nbytes, 'bytes')\n",
    "print('error medio:', round(float(np.abs(pesos - vuelta).mean()), 5))\n",
    "print('el peor    :', round(float(np.abs(pesos - vuelta).max()), 5))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La cuarta parte del espacio, y cada peso se movió menos de una centésima. Por\n",
    "eso funciona: **una red no necesita precisión, necesita direcciones**,\n",
    "y redondear un poquito cada peso casi no cambia hacia dónde apunta el conjunto.\n",
    "\n",
    "Cuando veas *GPTQ*, *AWQ* o *GGUF* en la descripción de un\n",
    "modelo descargable, están hablando de esto con más maña: eligen la escala por\n",
    "grupos de pesos en vez de una sola para todos, y miran qué pesos aguantan menos\n",
    "el redondeo. La idea de fondo es la de arriba.\n",
    "\n",
    "Y las otras dos palabras que salen siempre en la misma conversación, para que\n",
    "no te suenen a chino:\n",
    "\n",
    "- **El caché de claves y valores.** En el bucle que escribe, cada\n",
    "palabra nueva vuelve a mirar todas las anteriores. Guardar lo ya calculado en vez\n",
    "de rehacerlo es lo que hace que la respuesta salga fluida en vez de a\n",
    "tirones.\n",
    "\n",
    "- **Los modelos de expertos.** En vez de que todos los pesos\n",
    "participen en cada palabra, el modelo tiene muchos grupos y en cada paso enciende\n",
    "solo unos pocos. Sale un modelo enorme que cuesta como uno mediano cada vez que\n",
    "lo usas. Es lo que quieren decir con *MoE* o *mixture of\n",
    "experts*."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y el ajuste con personas, que es lo que lo hace contestar"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "De la lista de arriba, esta es la que más cambia lo que tú ves, así que la\n",
    "abro un poco más.\n",
    "\n",
    "Un modelo entrenado solo a predecir la palabra siguiente **no contesta\n",
    "preguntas: continúa texto**. Si le escribes \"¿cuál es la capital de Perú?\",\n",
    "una continuación perfectamente razonable es \"¿y la de Chile? ¿y la de Bolivia?\",\n",
    "porque en internet las preguntas suelen venir en listas.\n",
    "\n",
    "Que conteste en vez de continuar sale de una fase posterior en tres pasos:\n",
    "\n",
    "- Personas escriben respuestas buenas a un montón de preguntas, y el modelo se\n",
    "ajusta con esos ejemplos.\n",
    "\n",
    "- Personas comparan respuestas del modelo de a dos y dicen cuál prefieren. Con\n",
    "esas comparaciones se entrena otro modelo, cuyo único trabajo es puntuar qué tan\n",
    "buena es una respuesta.\n",
    "\n",
    "- El modelo grande se sigue ajustando para sacar buena nota con ese\n",
    "puntuador.\n",
    "\n",
    "A eso le dicen **RLHF**, aprendizaje por refuerzo con\n",
    "retroalimentación humana. Es la única parte del libro donde aparece el\n",
    "aprendizaje por refuerzo, que es la tercera familia de la que hablamos en el\n",
    "capítulo 1: ni supervisado ni no supervisado, sino\n",
    "aprender de una señal de qué tan bien lo hiciste.\n",
    "\n",
    "Y explica dos cosas que se notan usándolo. Que el modelo sea educado y\n",
    "prudente no es cosa de la arquitectura: es **gusto de las personas que\n",
    "compararon**. Y que a veces conteste con seguridad antes que decir \"no sé\"\n",
    "también, porque entre dos respuestas la gente tiende a preferir la que suena\n",
    "resuelta 😐"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Cuántos tokens gasta el español\n",
    "\n",
    "Parte varias palabras y cuenta los trozos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. La palabra que no se puede partir\n",
    "\n",
    "Prueba con una palabra cuyos trozos no estén todos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. La temperatura sobre el generador\n",
    "\n",
    "Escribe con temperaturas distintas y compara."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 3\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. La misma semilla siempre da lo mismo\n",
    "\n",
    "Comprueba que no hay nada mágico ni impredecible."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 4\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Cuántos pesos hay en cada sitio\n",
    "\n",
    "Reparte los parámetros de un bloque entre atención y\n",
    "feedforward."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 5\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Qué tan grande es la norma por capa\n",
    "\n",
    "Comprueba que deja cada fila en media 0 y desviación 1."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 6\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. El error del token que no existe\n",
    "\n",
    "Pídele al generador que arranque con una palabra que no\n",
    "está en el corpus."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 7\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "¿Por qué las conexiones residuales no son opcionales en un transformer profundo?\n",
    "\n",
    "a) Porque le dan al gradiente un camino corto de vuelta a las primeras capas\n",
    "\n",
    "b) Porque hacen el modelo más rápido\n",
    "\n",
    "c) Porque reducen el número de parámetros\n",
    "\n",
    "d) Porque evitan el sobreajuste"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El 92% que el modelo ya se sabía"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Evalúas el modelo con un banco de preguntas conocido, sale un 92% y ese número va a la propuesta. Es un banco público y respetado.\n",
    "\n",
    "```\n",
    "aciertos = 0\n",
    "for pregunta, respuesta in banco_publico:\n",
    "    if modelo(pregunta) == respuesta:\n",
    "        aciertos += 1\n",
    "\n",
    "# 92% de acierto\n",
    "```\n",
    "\n",
    "**¿Qué está mal?** La respuesta está en el cuaderno de soluciones. Míralo tú primero."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 🧩 El texto se parte en trozos, así que \"huancayo\" se lee como hu-an-ca-yo\n",
    "sin estar en el vocabulario.\n",
    "\n",
    "- ➕ Un bloque es atención más residual más norma más feedforward. Sin\n",
    "residual, seis bloques dejan todas las posiciones con parecido 1,0.\n",
    "\n",
    "- 🧠 Dos tercios de los pesos están en el feedforward, no en la atención.\n",
    "\n",
    "- 🔁 Escribir es un bucle: poner probabilidades, elegir, pegar, repetir. No hay\n",
    "plan previo.\n",
    "\n",
    "- 🌡️ La temperatura es una división: con 0,2 la favorita se lleva 0,9959 y con\n",
    "3,0 baja a 0,3774. No cambia el orden ni lo que el modelo sabe.\n",
    "\n",
    "- 🎚️ Top-k corta por cantidad y top-p por probabilidad acumulada, así que el\n",
    "corte del top-p se mueve según lo seguro que esté.\n",
    "\n",
    "- 🎲 Misma entrada y misma semilla dan la misma salida. Nada de esto es\n",
    "impredecible.\n",
    "\n",
    "- 📚 Y lo que aquí no está: el entrenamiento a escala, el ajuste con personas y\n",
    "las barreras.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un modelo de lenguaje no sabe lo que dice. Sabe qué palabra suele venir después, y eso explica casi todo lo que falla."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si lo que quieres es usar bien estos modelos en el día a día en vez de construirlos, eso es [prompt engineering](https://missyera.com/blog/prompts-que-funcionan/) 💬\n",
    "\n",
    "En el capítulo 18 pasamos al lado práctico: cuándo conviene un prompt, cuándo\n",
    "RAG y cuándo ajustar un modelo, que es la pregunta que llega a las empresas.\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 17 de **Deep learning desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/deep-learning-desde-cero/del-transformer-al-llm/\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
