Cadena de pensamiento: más tokens, más cómputo
25 min de lectura
La lección sobre la especificación le puso un techo a cualquier prompt: cuando ya no queda duda sobre la tarea, la probabilidad de cumplir es la de esa tarea con ese caso, y escribir mejor no la sube. Aquella cuenta tomaba al modelo por una imitación perfecta de su texto. Una red de verdad imita con un trabajo fijo por token: escribe de token en token, y a cada uno le dedica una pasada por las mismas capas, sea una coma o la cifra que decide el problema. Así que lo que el modelo hace bien depende también de cuántos tokens escribe antes de comprometerse con la respuesta.
Tres cubiletes boca abajo y una bola bajo el de la izquierda. El trilero cambia el de la izquierda con el del centro, luego el del centro con el de la derecha, y luego otra vez el de la izquierda con el del centro. ¿Dónde está la bola? Un modelo que contesta de entrada tiene que decidir a la derecha en la pasada que escribe su primer token: los tres cambios, uno detrás de otro, resueltos dentro de las capas que tenga. Uno que antes escribe al centro; a la derecha; sigue a la derecha resuelve cada cambio en las pasadas de su propio trozo de texto, a partir de lo que dejó escrito el anterior. ¿Qué compra ese texto, y cuánto cuesta?
Ese texto previo es la cadena de pensamiento (chain of thought, CoT): los tokens que un modelo escribe entre la pregunta y la respuesta, con los pasos intermedios. La pide el prompt, con una frase como Piensa paso a paso antes de responder. o con ejemplos que la lleven, y lo que cambia no está en la red. Cambia el recorrido. Lo que una pasada calcula se pierde al acabar, salvo dos cosas: las claves y los valores que deja en la caché, que las pasadas siguientes leen capa por capa, a la misma altura, y el token que sale sorteado, que vuelve a entrar por abajo. Escribir un paso es dejar su resultado donde la pasada siguiente puede empezar a trabajar con él.
Una pasada, una profundidad fija
Fijemos la red de la lección sobre la caché, con capas y una fila por posición en cada una. Llamemos a la fila de la posición a la salida de la capa , y a la de entrada, el embedding del token más el vector de su posición. Por la máscara causal, se calcula con las filas y con nada más: la suma residual también viene de la capa de abajo.
Un camino hasta los logits es una sucesión de filas en la que cada una entra en el cálculo de la siguiente. Cada flecha sube exactamente una capa, así que todos tienen la forma
y cruzan capas, ni una más, sea cual sea . Llamemos profundidad de un cálculo al mayor número de capas que uno de sus caminos cruza en serie. La de los logits de cualquier posición es . Un prompt más largo le da a cada capa más filas que leer, más anchura, y ni una capa más de profundidad. La caché tampoco la añade: lleva hacia delante las claves y los valores de cada posición, pero de la capa a la , nunca hacia abajo.
Generar cambia eso en un solo sitio. El token se sortea con , que sale de , y entra en : un camino puede subir las capas en la posición , salir por el sorteo y volver a entrar por abajo en la siguiente. Si entre la pregunta, de tokens, y la respuesta el modelo escribe una cadena de tokens, el primer token de la respuesta se sortea con , y el camino más largo desde la pregunta hasta él cruza
con sorteos por medio. Sin cadena, . La red es la misma, y la profundidad de lo que puede calcular antes de responder se ha multiplicado por .
Lo único que vuelve abajo es un token
Por cada vuelta pasa un token y nada más. El resto de (64 números en el mini-GPT) sólo sirve para los logits de su posición. Un token es una entrada de , así que lleva como mucho
9 en el mini-GPT, unos 17 con un vocabulario de 100 000 entradas. Por eso una cadena sirve en la medida en que sus tokens llevan los resultados intermedios, como a la derecha lleva dónde está la bola. Un token que el modelo escribe siempre igual, un punto tras otro, no lleva nada hacia abajo: el camino existe y no transporta información. Pfau y sus coautores entrenaron modelos que resuelven dos tareas con una fila de puntos entre la pregunta y la respuesta, y sin ella fallan; lo que esos puntos les dan es anchura, más posiciones calculando a la vez en cada capa, y aprender a usarla les costó una supervisión muy específica.
Que la profundidad importe, es decir, que haya problemas que ninguna red de capas resuelve en una pasada para entradas de cualquier longitud y que una cadena sí resuelve, es teoría de la complejidad, con hipótesis que se creen ciertas y que quedan fuera del curso. Merrill y Sabharwal reúnen problemas así de sencillos (seguir un autómata finito, una máquina de estados como la bola del trile) y demuestran que con una cadena tan larga como la entrada un Transformer los resuelve. El argumento de arriba dice qué hace posible una cadena. No dice que un modelo concreto lo aproveche.
Lo que cuesta una cadena
Cada uno de los tokens es una pasada, con el coste por token de la lección sobre la caché: multiplicaciones en la posición , una por cada peso de las matrices más la atención sobre las posiciones. La cadena ocupa de la posición a la , y con ,
Las matrices cobran lo mismo por cada token, y la atención, más cuanto más tarde llega: lo primero crece en línea recta con , y lo segundo, en una cadena larga, con . La respuesta, que ahora empieza posiciones más tarde, paga multiplicaciones más por cada uno de sus tokens.
Las multiplicaciones no son toda la factura. La caché dejó medido que un token escrito no comparte pasada con el siguiente, que no existe hasta que se sortea, mientras que los del prompt entran juntos y se reparten el coste fijo de cada pasada. Los tokens de la cadena son de los caros: el tiempo hasta la respuesta crece con al ritmo de escribir, no al de leer. Quien sirve un modelo a través de una API (application programming interface) suele cobrar el token generado varias veces más caro que el leído, por esa misma razón, y hay quien no enseña la cadena de su modelo y la cobra igual.
Lo que compra, según los modelos grandes
Que una cadena compre algo se ha medido, y en modelos mucho mayores que el tuyo. Wei y sus coautores pusieron en el prompt ocho ejemplos con su cadena escrita, y un modelo de 540 000 millones de parámetros pasó de resolver el 17.9 % de los problemas de GSM8K (Grade School Math, unos 8 500 problemas de matemáticas de primaria) a resolver el 56.9 %. Kojima y los suyos lo consiguieron sin ejemplos, con una frase delante de la respuesta, Let's think step by step: en otro conjunto de problemas aritméticos, del 17.7 % al 78.7 %.
El primero encontró también el límite. La cadena sólo ayuda desde unos 100 000 millones de parámetros, y por debajo de 10 000 millones suele empeorar el acierto, porque el modelo escribe cadenas fluidas y sin lógica. Tu mini-GPT tiene 136 448.
Tampoco hay que leer la cadena como el registro de lo que la red calculó. Turpin y sus coautores reordenaron las opciones de los ejemplos de un prompt para que la buena fuera siempre la (A); los modelos eligieron la (A) también donde era falsa, escribieron cadenas que lo justificaban, y rara vez mencionaban el orden. El texto es lo que pasa de una pasada a la siguiente; si es además la razón de la respuesta, hay que medirlo. Y los modelos que hoy escriben cadenas largas antes de responder no lo hacen por una frase del prompt: los entrenaron para ello con recompensas que se comprueban, como contó la lección sobre el origen de las preferencias, y pagan esta factura en cada pregunta.
Tres clases de afirmaciones, entonces. Lo que cuesta una cadena se mide, y la celda de abajo lo mide en el navegador. Lo que hace posible se argumenta, con el camino. Que mejore las respuestas se cita.
La factura, medida en el mini-GPT
La celda corre sobre tu mini-GPT del bloque 1 y no sobre el modelo guionizado: lo que mide es el coste de un token, y eso es cosa de la red, por la que el modelo guionizado no pasa. Le da la pregunta del trile reducida a ¿Dónde está la bola?, porque en su ventana de 64 tokens no caben el enunciado entero y una cadena, y le deja escribir tokens antes del primero de la respuesta. Después le da esos mismos tokens ya escritos, al final del prompt, para que los lea en una pasada. Mira tres cosas: las pasadas, el reloj y las dos últimas líneas.
import numpy as np
from pyodide.http import open_url
exec(open_url("/courses/llm-agents/modelo.py").read()) # red, F, llenar, avanzar, codificar
L, d = red.cfg["n_capas"], red.cfg["d_model"]
c_fila = L * (4 * d * d + 2 * d * red.cfg["d_ff"]) + d * red.cfg["n_v"]
c = lambda t: c_fila + 2 * L * t * d # el token de la posición t, con caché
pregunta = codificar("¿Dónde está la bola?", F)
T = len(pregunta) # 12 tokens
def escrita(R, semilla):
"""El mini-GPT escribe R tokens de cadena: una pasada por la pregunta y otra por token."""
rng = np.random.default_rng(semilla)
z, cache = llenar(red, pregunta)
cadena = []
for _ in range(R):
cadena.append(muestrear(z, 1.0, None, 0.9, rng))
z = avanzar(red, cadena[-1], cache)
return cadena, z # z: logits del primer token de la respuesta
def leida(cadena):
"""Los mismos tokens, ya escritos, al final del prompt: una sola pasada."""
return llenar(red, pregunta + cadena)[0]
def ms(f, veces=5):
"""La mediana de varias medidas de f(), en milisegundos."""
medidas = []
for _ in range(veces):
t0 = time.perf_counter(); f(); medidas.append(1000 * (time.perf_counter() - t0))
return np.median(medidas)
escrita(48, 0); leida(list(range(48))) # calentamiento: la primera vez es lenta
miles = lambda n: format(n, ",").replace(",", " ")
print(" R multiplicaciones pasadas: escrita leída ms: escrita leída capas: escrita leída")
dif = 0
for R in [0, 12, 24, 48]:
cadena, z = escrita(R, semilla=0)
dif = max(dif, np.abs(z - leida(cadena)).max())
print("%3d %18s %16d %5d %12.0f %5.0f %14d %5d" % (R, miles(sum(c(t) for t in range(T + 1, T + R + 1))),
1 + R, 1, ms(lambda: escrita(R, 0)), ms(lambda: leida(cadena)), (R + 1) * L, L))
print("\nlogits de la respuesta, escrita o leída la cadena: difieren en %.0e" % dif)
print("la cadena de 48 tokens:", repr(decodificar(cadena, F)))
La primera ejecución descarga el intérprete de Python (~15 MB). Después queda en la caché del navegador y se reutiliza en todas las lecciones.
La columna de multiplicaciones es la fórmula de arriba con : para , , lo mismo con la cadena escrita que leída, porque pasan las mismas filas por las mismas matrices. El reloj no está de acuerdo. Escrita, la cadena tarda en proporción a , una pasada por token; leída, es una sola pasada que reparte su coste fijo, y con sale varias veces más rápida. Tus milisegundos serán otros, y la proporción, parecida.
La penúltima línea es la que más importa. Los logits del primer token de la respuesta son los mismos,
salvo el redondeo, tanto si el mini-GPT escribió la cadena como si se la diste escrita: a la
respuesta le llega lo mismo, y la profundidad de la última columna dice quién hizo el trabajo. Una
cadena leída es un trabajo que hizo otro. La última línea es la cadena: frases a medio hacer con el aire
de Marianela, sin rastro de ninguna bola. Costó lo mismo que una cadena que la siguiera, porque la factura no lee lo que
paga. Cambia la semilla de escrita en el bucle y compruébalo: otra cadena, la misma columna.
Queda una consecuencia para el harness. La especificación de las fechas pedía la fecha y nada más, y una cadena delante la incumple por definición: pensar y no escribir nada más que la respuesta son dos peticiones incompatibles. La especificación tiene que decir dónde va la respuesta (en la última línea, tras Fecha:, por ejemplo), y la comprobación tiene que leerla ahí. El orden no es indiferente: una fecha escrita antes que los pasos ya está sorteada cuando llegan, y no hay cadena que la cambie.
Comprueba tu intuición
Cuatro preguntas: la profundidad, leer frente a escribir, lo que demuestra la celda y dónde va la respuesta.
El mini-GPT tiene capas. Ante una pregunta de tokens escribe una cadena de 30 tokens y después la respuesta. ¿Cuántas capas cruza, como mucho, un camino que va de la pregunta al primer token de la respuesta?
Se acepta un margen de ±0.
En vez de dejar que el modelo escriba la cadena, le das tú esos mismos tokens, ya escritos, al final del prompt. Marca lo que es cierto.
Marca todas las opciones correctas. Se corrige todo o nada: no hay puntuación parcial.
La celda corre sobre el mini-GPT. Marca lo que demuestra.
Marca todas las opciones correctas. Se corrige todo o nada: no hay puntuación parcial.
Quieres que el modelo de las fechas piense antes de responder, y el programa sigue necesitando una fecha AAAA-MM-DD que pueda comprobar. ¿Qué especificación eliges?
Una cadena le da a la red profundidad a cambio de tokens, y deja al programa un problema que antes no tenía: la respuesta llega al final de un texto libre, y hay que encontrarla. Pedir su forma en el prompt sube la masa en las respuestas que la respetan, pero no la lleva a 1, y cada respuesta que se sale de ella es una llamada perdida, con su cadena pagada.
La lección siguiente, sobre la salida estructurada, deja de pedir la forma y la impone en el sorteo: en cada posición, los tokens que la romperían reciben probabilidad cero antes de sortear. Funciona con cualquier modelo que dé logits, también con tu mini-GPT, al que ningún prompt le saca una respuesta con forma fija.
Para profundizar5 fuentes · 5 papers
De dónde sale lo de esta lección, y dónde seguir si quieres más. Nada de aquí hace falta para continuar el curso.
- Chain-of-Thought Prompting Elicits Reasoning in Large Language Models
Ocho ejemplos con su cadena en el prompt llevan un modelo de 540 000 millones de parámetros del 17.9 % al 56.9 % en GSM8K. Su figura de la escala es el límite de esta lección: por debajo de unos 100 000 millones, no ayuda.
- Large Language Models are Zero-Shot Reasoners
Sin ejemplos, con una frase escrita delante de la respuesta: «Let's think step by step». Con InstructGPT, del 17.7 % al 78.7 % en MultiArith. La cadena se pide con una frase; la factura es la misma.
- The Expressive Power of Transformers with Chain of Thought
La versión rigurosa del argumento de la profundidad: cuánto amplía una cadena lo que un Transformer puede calcular, según su longitud. Con tantos pasos como la entrada, ya sigue un autómata finito cualquiera.
- Let's Think Dot by Dot: Hidden Computation in Transformer Language Models
Modelos que resuelven dos tareas con una fila de puntos antes de la respuesta, y sin ella fallan: más posiciones calculando a la vez, sin que ningún token lleve un paso. Aprender a usarla les costó una supervisión muy específica.
- Language Models Don't Always Say What They Think: Unfaithful Explanations in Chain-of-Thought Prompting
Con ejemplos sesgados para que la respuesta buena sea siempre la (A), los modelos eligen la (A) y escriben una cadena que lo justifica sin nombrar el sesgo. La cadena no es el registro de lo que calculó la red.