Transformers and local inference
A course from zero on the transformer and on running one, built around a single request: a clinical question over a 6,000-token synthetic patient history, sent to Qwen3.6 35B-A3B on a laptop with a Radeon 860M and 64 GB of shared memory. It builds the transformer as training sees it, whole matrices in which every token meets every weight, and then shows that at inference almost all of it is frozen except one new column, with its keys and values cached and its query thrown away. The hardware half follows the weights from HBM to the tensor cores of an H100 for a dense 7B, where every step streams all 14 GB whatever the number of tokens, and derives from that the two phases of inference, the balance point near 295 tokens, batching, chunked prefill and the KV cache in bytes. Static schematics, each stating its domain, replace the earlier simulations; two interactive figures remain, and a standalone simulator follows the whole request.
Por qué este artículo26 · oct 27Read notehe leído mucho sobre transformers, y casi todo cae en uno de dos montones. El primero es matemático: correcto, lleno de índices, y nunca enseña la cosa moviéndose, así que acababa un paper sabiéndome la fórmula de la atención y sin ninguna imagen de lo que estaba haciendo el chip. El segundo simplifica hasta que se pierde la utilidad de bajo nivel (el modelo presta atención, la caché recuerda), y nada de lo que cuenta te deja predecir un solo número. Entre los dos faltan intuiciones, sin más, y casi todas son de orden y de tiempo: qué se calcula una vez y qué en cada paso, a qué token pertenece un vector, en qué fase estamos, qué pieza del hardware trabaja mientras otra espera. Muchas explicaciones son verdad en el dominio de tiempo equivocado; la imagen de matrices enteras que dibuja cualquier diagrama de la atención es exacta para el entrenamiento y casi todo movimiento perdido para un modelo que está escribiendo. Así que rehago este artículo una última vez, con un lenguaje menos serio de lo que suelo permitirme, porque estas intuiciones llegan mejor contadas como las contaría en una mesa.
Compré el portátil que tengo en la mesa para ejecutar en local modelos como el de este artículo, y casi todo lo que sigue depende de su memoria, así que se empieza por ahí. Es un Lenovo ThinkPad con un AMD Ryzen AI 7 PRO 4501, y su único procesador gráfico es la Radeon 860M integrada en el mismo chip, sin memoria propia: el procesador y la GPU comparten los mismos 64 GB de LPDDR5X (memoria unificada). Ese depósito decide qué modelos caben. Qwen3.6 35B-A3B, la mezcla de expertos que sigue este artículo, ocupa 21,7 GB en Q4_K_M; el denso Qwen3.8 27B ocupa 16,8 GB; un 70B como Llama 3.1 sale por unos 43 GB en el mismo formato y solo cabe cuando el navegador y compañía devuelven memoria, porque Windows y mis aplicaciones de siempre ya ocupan 32 GB2.
1AMD, página del Ryzen AI 7 PRO 450: 8 núcleos, 16 hilos, NPU de hasta 50 TOPS. 2Meta, Llama 3.1 70B: 70.600 millones de parámetros, que a los 4,89 bits por peso de Q4_K_M dan 43 GB.
Lo que ese depósito no decide es la velocidad. Entrega unos 128 GB/s, y la iGPU lee de él cada peso por ese bus, en cada token; una H100 lee su propia HBM a 3,35 TB/s, unas 26 veces más rápido, y esa proporción va por debajo de todos los números de la parte de hardware. El modelo lo sirve Ollama. Las velocidades salen de un día que pasé midiendo este portátil, los detalles del modelo de su propio archivo, y TokenWalk, un simulador aparte que construí junto a este artículo, sigue una petición desde el disco hasta el último token.
Esa petición es de las que recibiría un copiloto clínico: ¿alguna interacción con el anticoagulante nuevo?, detrás de un prompt de sistema corto y de una historia de paciente sintética de unos 6.000 tokens, así que los datos del paciente no salen de la máquina. En este portátil la respuesta tarda algo más de un minuto en empezar y luego sale a unas doce palabras por segundo, durante unos 300 tokens, y el resto del artículo explica esos dos números en el orden en que los entendí.
El transformer como matrices enteras
Esta primera parte es el transformer como lo ve el entrenamiento: todos los tokens de una secuencia atraviesan todas las capas a la vez, y cada peso se cruza con cada token. Casi todas las explicaciones se mudan aquí, compran muebles y no se vuelve a saber de ellas.
Productos escalares y matrices
Todo lo que un modelo de lenguaje hace a lo grande es una sola operación, repetida miles de millones de veces. Coges dos listas de números de la misma longitud, las multiplicas posición a posición y sumas los resultados. Con x=(0,8;0,1;0,5)x = (0{,}8; 0{,}1; 0{,}5)x=(0,8;0,1;0,5) y w=(0,31;−1,2;0,07)w = (0{,}31; -1{,}2; 0{,}07)w=(0,31;−1,2;0,07) ese producto escalar es 0,8⋅0,31+0,1⋅(−1,2)+0,5⋅0,07=0,1630{,}8 \cdot 0{,}31 + 0{,}1 \cdot (-1{,}2) + 0{,}5 \cdot 0{,}07 = 0{,}1630,8⋅0,31+0,1⋅(−1,2)+0,5⋅0,07=0,163. Lo leo como una pregunta, cuánto se parece xxx a www: sale grande cuando apuntan igual, cero cuando son perpendiculares, negativo cuando se oponen. Una neurona de una red neuronal es exactamente esto, un producto escalar de sus entradas con sus pesos seguido de una función que corta la parte negativa, y los pesos son lo que eligió el entrenamiento. Un modelo de 35B parámetros tiene 35.000 millones de estos números.
Pon muchas neuronas una al lado de otra, leyendo todas la misma entrada, y tienes una matriz: cada fila son los pesos de una neurona, y multiplicar la matriz por un vector calcula de golpe el producto escalar de cada fila. Un modelo de lenguaje son unos cientos de matrices así, aplicadas una detrás de otra. Para multiplicar un vector por una matriz, cada peso se trae de memoria una vez y se usa para una multiplicación y una suma. Multiplica dieciséis vectores por la misma matriz y cada peso se sigue trayendo una vez, pero se usa dieciséis. Guárdate esa frase en el bolsillo (toda la parte de hardware del artículo es esa frase con una GPU puesta). Para contar, un token que atraviesa un modelo cuesta unas dos operaciones por cada peso que toca:
FLOPs por token≈2×Pactivos\text{FLOPs por token} \approx 2 \times P_{\text{activos}}FLOPs por token≈2×Pactivos
Nuestro modelo toca unos 3.000 millones de pesos por token (los expertos explican por qué), así que unas 6.000 millones de operaciones.
Tokens y embeddings
Una matriz quiere un vector, y nosotros tenemos texto. Un tokenizador corta el texto en piezas de un vocabulario fijo y le da a la red un entero por pieza: las palabras comunes son una pieza, las raras varias, y un espacio delante suele ir con la palabra que sigue. El vocabulario de Qwen3.6 tiene 248.320 entradas. Se aprenden una vez, antes del entrenamiento, con BPE: se empieza por bytes sueltos, se pega en una entrada nueva la pareja de vecinos que más aparece en un corpus, y se repite (el archivo de Qwen guarda sus 247.587 fusiones en orden). Es una de las dos figuras que he dejado interactivas, porque la gracia es escribir tu propio texto: entrena un tokenizador con un solo párrafo clínico y corta lo que escribas.
characters63 UTF-8 bytes63 words12 tokens25 vocabulary161 A byte-pair tokenizer trained on one paragraph, keeping 120 merges on top of 41 characters, cuts the sentence into 25 pieces: T | he | ·patient | ·present | s | ·with | ·f | ev | er | ·and | ·a | ·dr | y | ·co | ug | h | ·f | or | ·th | re | e | ·d | ay | s | .. As characters it would be 63 pieces, and a model writes one piece per pass through all its layers.
Con letras sería más simple y costaría más. El inglés y el español van a unos cuatro caracteres por token, y cada posición escrita es una pasada entera por el modelo, así que una respuesta de 300 tokens llevaría unas 1.200 pasadas.
Un chat es una lista de mensajes y el modelo solo continúa texto, así que una plantilla de chat que viene en el archivo del modelo aplana la lista en una cadena, con tokens especiales que marcan quién habla. Qwen usa ChatML, y el modelo escribe a partir de la última línea, así que el prompt de sistema no es más que las primeras líneas de un texto largo:
TEXT Copy
<|im_start|>system You are a clinical assistant. Cite the record.<|im_end|> <|im_start|>user Any interaction with the new anticoagulant?<|im_end|> <|im_start|>assistant
Cada ID de token se convierte después en un vector buscándolo en una tabla: la matriz de embeddings tiene una fila por entrada del vocabulario, de 2.048 números de ancho en Qwen3.6, y el entrenamiento ha ido empujando esas filas hasta que los tokens relacionados apuntan en direcciones relacionadas. Apila los vectores de un prompt de nnn tokens y tienes una matriz XXX de n×2.048n \times 2{.}048n×2.048. Los esquemas y el texto llaman columna al vector de cada token; en las fórmulas es una fila de XXX, los mismos números.
Buscados uno a uno, perro muerde a hombre y hombre muerde a perro dan los mismos vectores, así que algo tiene que llevar el orden. GPT-3 suma a cada embedding un vector de posición aprendido al entrar. Qwen usa RoPE, un tipo de codificación posicional que gira partes de cada consulta y cada clave dentro de cada capa de atención un ángulo proporcional a la posición, de modo que compararlas depende de lo lejos que estén (su base de rotación de diez millones le ayuda a llegar a 262.144 tokens de contexto).
La atención
El vector de cada token entra ahora en el flujo residual, un canal que recorre el modelo de abajo arriba. Cada bloque lee una copia normalizada y suma su resultado encima (una conexión residual), así que la columna de un token es una suma acumulada del embedding y, en cada capa, la aportación de la atención y luego la del MLP. La palabra modelo tiene que significar una cosa en un modelo de machine learning y otra en una modelo de pasarela; la atención es el bloque que deja que machine learning escriba en la columna de modelo.
Una cabeza de atención tiene sus propias tres matrices aprendidas, WQhW_Q^hWQh, WKhW_K^hWKh y WVhW_V^hWVh, y multiplica el vector completo de cada token por cada una, lo que da a cada token una consulta, una clave y un valor de 256 números en Qwen3.6. La consulta es lo que un token busca, la clave lo que anuncia, el valor lo que entrega si lo eligen. En la imagen de libro todo el prompt va a la vez: Q=XWQhQ = XW_Q^hQ=XWQh, K=XWKhK = XW_K^hK=XWKh, V=XWVhV = XW_V^hV=XWVh, cada una de n×256n \times 256n×256. En el código las matrices de las cabezas van una al lado de otra en una WQW_QWQ ancha (en GPT-3, 12.288×12.28812{.}288 \times 12{.}28812.288×12.288 para 96 cabezas de 128), y el resultado se corta en cabezas después de la proyección, así que cada cabeza ve la entrada entera1.
1Brown et al., Language Models are Few-Shot Learners, 2020, tabla 2.1: 96 capas, un flujo residual de 12.288 y 96 cabezas de 128.
Después cada consulta se cruza con cada clave. QKTQK^TQKT es una matriz de n×nn \times nn×n puntuaciones, una fila por token, y un softmax en cada fila las convierte en pesos entre 0 y 1 que suman 1. Aquí va una fila, la de un solo token, con consultas y claves que escribí a mano para que el pronombre tenga a quién encontrar.
claveq·kpeso (softmax)valor vla consulta de its (token 7) pregunta a cada clave hasta ella1 Amiodarone0.64 %2 raises0.13 %3 warfarin3.474 %4 levels0.64 %5 so0.23 %6 lower0.96 %7 its0.85 %8 dose−∞enmascarada: va después, peso 0Σ peso × v =salida de its en esta cabeza: 1 × 256 Una cabeza de una capa en el token 7, con puntuaciones de juguete. Los pesos suman 1; lo que viaja es el contenido de v, sobre todo el valor de warfarin.
Desde its, el 74% del peso cae en warfarin, que es más concentración de la que yo he conseguido con nada este año. Una cabeza de verdad aprende sus vectores, pero la aritmética es la misma, con dkd_kdk la longitud de una clave (dividir por su raíz cuadrada evita que los vectores largos vuelvan el softmax todo o nada):
Attention(Q,K,V)=softmax (QKTdk)V\text{Attention}(Q, K, V) = \operatorname{softmax}\!\left(\frac{QK^{T}}{\sqrt{d_k}}\right) VAttention(Q,K,V)=softmax(dkQKT)V
Lee el último producto token a token, como hace el esquema. La salida de un token en una cabeza es su fila de pesos por VVV, 256 números, cada uno esa fila de pesos multiplicada escalarmente por una columna de VVV: una mezcla de los valores de los otros tokens en las proporciones que eligió el softmax. Las puntuaciones solo fijan las proporciones; el contenido que viaja está en VVV. Esta suma ponderada es el único sitio de la red donde la información de un token llega a otro. Las proyecciones, la normalización, la matriz de salida y el MLP van posición a posición, token a token, sin mirar a los demás.
Una capa ejecuta varias cabezas en paralelo (atención multicabeza), cada una libre de captar una relación distinta. Sus salidas se concatenan y se multiplican por una matriz más, WOW_OWO, que las vuelve a mezclar al ancho del flujo, y el resultado se suma al residuo del token que preguntó, y solo al de ese token (en GPT-3, 96 cabezas de 128 concatenadas dan justo 12.288). Qwen3.6 tiene 16 cabezas de consulta por capa de atención pero solo 2 juegos de claves y valores, cada uno compartido por 8 cabezas: grouped-query attention, que guarda ocho veces menos claves y valores a cambio de un poco de calidad1.
1Ainslie et al., GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints, 2023.
La sorpresa del archivo de Qwen3.6 es full_attention_interval = 4: solo diez de sus cuarenta capas son esta atención. Las otras treinta son capas Gated DeltaNet, una atención lineal que guarda un estado de tamaño fijo por capa, que cada token actualiza como en una red recurrente1. Son la otra forma que tiene Qwen de mezclar tokens; un estado fijo no puede volver a un token anterior concreto, así que algunas capas de atención completa se quedan.
1Yang, Kautz y Hatamizadeh, Gated Delta Networks: Improving Mamba2 with Delta Rule, ICLR 2025.
En el esquema, its nunca mira a dose, la palabra que va después. En un modelo que escribe texto, cada puntuación en la que una consulta se encuentra con una clave posterior se pone a menos infinito antes del softmax, y e−∞=0e^{-\infty} = 0e−∞=0, así que la matriz de puntuaciones es un triángulo: la máscara causal. Existe por el entrenamiento (una frase de ocho palabras da ocho tareas de predicción en una pasada, y ninguna puede mirar su respuesta), y resulta ser la regla con más consecuencias de este artículo. El transformer original era un encoder-decoder, con una pila sin máscara para la frase de origen y atención cruzada hacia ella; un modelo decoder-only como Qwen recibe el origen en su prompt y lo lee con atención enmascarada, porque está en el pasado.
El MLP y sus expertos
Tras la atención, cada columna pasa sola por un MLP, dos o tres productos de matrices con una no linealidad en medio. La primera matriz se lee como una lista de patrones, cada uno preguntando cuánto se le parece la columna; la no linealidad pone a cero las puntuaciones bajas; la segunda matriz suma una dirección por cada patrón que se activó. Un MLP guarda decenas de miles de estas parejas de patrón y dirección por capa, y ahí parece vivir casi todo lo que sabe un modelo (y ahí lo deja casi siempre el postentrenamiento, como defiende el artículo sobre cómo aprenden los LLM)1; en un modelo denso, los MLP son unos dos tercios de los parámetros.
1Geva et al., Transformer Feed-Forward Layers Are Key-Value Memories, 2021; Meng et al., Locating and Editing Factual Associations in GPT, 2022.
Una mezcla de expertos parte ese MLP en muchos pequeños y añade un router. Qwen3.6 tiene 256 expertos en cada una de sus 40 capas, cada uno tres matrices de 2.048×5122{.}048 \times 5122.048×512; el router puntúa los 256 para el token actual, se ejecutan los 8 mejores, y un experto compartido se ejecuta para todos los tokens. El router es una matriz pequeña más, con sus valores fijados en el preentrenamiento por la misma retropropagación que cualquier otro peso. De ahí el nombre: 35.000 millones de parámetros, unos 3.000 millones activos por token. Un token escrito despierta 8 expertos por capa más el compartido, mientras que un trozo de prompt de 512 tokens, cada uno eligiendo sus ocho, despierta casi los 256. Todos los expertos siguen en memoria, porque el token siguiente puede elegir cualquiera, así que el ahorro está en lo que se lee y se calcula por token, y es máximo cuando hay un solo token en vuelo, que es cuando el modelo escribe.
La imagen dibuja una capa entera con los tamaños de GPT-3 y, como las otras dos figuras grandes, está hecha con Manim, el motor de animación de código abierto que Grant Sanderson escribió para 3Blue1Brown1. Sigue la única línea que sube por el medio, que es el flujo, y las dos ramas que salen de ella y vuelven como sumas.
1Su recorrido visual por los transformers sigue siendo el mejor sitio para coger intuición sobre la atención. Los esquemas pequeños son SVG dibujados por el propio código del artículo, y la nube de puntos del ancho de banda sale de los datos de benchmark de TokenWalk.
Una capa sobre el flujo residual, dibujada con los tamaños reales de GPT-3, no los de Qwen. Atención: LayerNorm, 96 cabezas en paralelo, concatenación (96 × 128 = 12.288), la matriz de salida y una suma al flujo. MLP: LayerNorm, W1 (12.288 × 49.152), GELU, W2 y una segunda suma. El embedding y la posición solo entran abajo; tras 95 capas más, una última LayerNorm y la matriz de salida al vocabulario (12.288 × 50.257) alimentan el softmax sobre el vocabulario, que no es un softmax de atención.
Muestreo del siguiente token
Tras la última capa, la palabra siguiente depende de una sola columna, el residuo final del último token. La cabeza LM la multiplica en 248.320 puntuaciones, una por cada posible token siguiente, los logits, y un softmax los convierte en probabilidades después de dividir cada logit por una temperatura TTT:
pi=ezi/T∑jezj/Tp_i = \frac{e^{z_i / T}}{\sum_j e^{z_j / T}}pi=∑jezj/Tezi/T
Toma siete candidatos para la palabra que sigue a lower the warfarin, con logits que me he inventado, desde 6,1 para dose hasta −1,5 para banana. Con T=1T = 1T=1, dose se lleva el 65%, INR el 20% y levels el 10%. Con 0,5 el favorito se lleva el 90%; con 2 baja al 42% y banana sube del 0,03% a casi el 1%, que en un copiloto clínico es más o menos cómo te despiden. El orden no cambia nunca. En el límite de cero el primer token se lo lleva todo, que es la decodificación voraz. El top-p en cambio se queda con el conjunto más pequeño desde arriba cuyas probabilidades llegan a ppp y lo renormaliza (con p=0,9p = 0{,}9p=0,9 y T=1T = 1T=1, dose, INR y levels, que juntos suman el 95%), porque muestrear de la distribución entera elige de vez en cuando un token de la cola larga que descarrila el resto del texto1.
1Holtzman et al., The Curious Case of Neural Text Degeneration, ICLR 2020.
La temperatura cambia la forma de toda la distribución y deja viva la cola; el top-p corta la cola y deja en paz las proporciones de los que sobreviven. El archivo de Qwen3.8 recomienda temperatura 1,0, top-p 0,95 y top-k 20 (un número fijo de candidatos), y nadie sabe cuáles son los mejores hasta que están hechos los 248.320 productos escalares. Claude ya no acepta ninguno de estos ajustes: desde la versión de la API 2026-10-21, una petición que los fija devuelve un error1. El mismo muestreador funciona también dentro del entrenamiento, porque el RL hace que el modelo escriba sus respuestas a una temperatura antes de puntuarlas: GRPO muestrea 16 respuestas por prompt y solo aprende cuando difieren.
1Anthropic, API version 2026-10-21 migration guide, cambio 1, que recomienda guiar el comportamiento con el prompt.
Entrenamiento
El entrenamiento es el bucle que elige los pesos. Se predice el token siguiente en cada posición de un lote de texto y se puntúan las predicciones con un número, la pérdida, aquí la entropía cruzada: menos el logaritmo de la probabilidad que se dio al token que de verdad venía, así que un error con mucha confianza es lo que más cuesta. La retropropagación calcula hacia dónde debe moverse cada parámetro para bajarla, y cada uno da un paso pequeño. Gracias a la máscara causal, cada posición es un ejemplo distinto dentro de una sola pasada en paralelo; GPT-3 se entrenó con 300.000 millones de tokens en lotes de 3,2 millones, unos 94.000 pasos1. Todo lo que viene después del preentrenamiento, del ajuste fino con adaptadores LoRA al RLHF, está en el artículo sobre cómo aprenden los LLM, que conserva este bucle y solo cambia de dónde sale la señal, y enseña por qué un paso de RL es mitad decode y mitad esta pasada de matrices enteras; aquí basta con que la inferencia nunca cambia los pesos, así que el modelo sabe de tu paciente solo lo que va en la petición.
1Brown et al., 2020, citado arriba, tabla 2.1.
Este es el transformer como lo dibujan todos los diagramas: matrices enteras, cada peso cruzándose con cada posición en una pasada, un triángulo completo de n×nn \times nn×n puntuaciones por cabeza. Para entrenar y para leer un prompt es exactamente así. Para un modelo que escribe su respuesta, es casi todo movimiento perdido.
Una sola columna en movimiento
Cambio de dominio: ahora estamos en el decode, el paso que escribe un token nuevo de la respuesta, y seguimos a ese token por todas las capas. Aquí va la frase de la sección de atención con su máscara, primero como la calcula el entrenamiento (todas las filas a la vez) y después como lo hace un modelo que escribe cuando llega la octava palabra.
filas: la consulta de cada palabra · columnas: claves 1 a 8123456781 Amiodarone1002 raises77233 warfarin3327404 levels102059125 so25111430206 lower1012261721147 its437443658 dose43247416366pasosanteriores:congeladaseste pasoceldas vacías: −∞ antes del softmax, 0 después (la máscara)
Una cabeza de una capa, en el paso que añade el token 8. El prefill o el entrenamiento calculan las 8 filas de una vez, un softmax por fila; el decode solo calcula la fila nueva, y las de arriba no cambian nunca.
Añade una palabra y aparece exactamente una fila nueva; las de arriba no cambian ni un dígito, porque ninguna de esas palabras podía mirar a la nueva. Ese fue el momento en que me hizo clic, y vale a cualquier altura del modelo, por inducción sobre las capas. En la capa 1, un token anterior solo leyó tokens hasta él mismo, así que su columna tras la capa 1 no puede cambiar ya; la capa 2 solo lee esas columnas terminadas, así que su columna tras la capa 2 también queda terminada, y así hasta arriba. La columna de cada token anterior, en cada capa, queda hecha para siempre en cuanto se calcula. Nadie del pasado mira hacia delante; solo el token nuevo mira hacia atrás.
Repasa lo que eso congela. La clave y el valor de un token anterior en cada capa los van a leer todos los tokens posteriores y no cambian nunca, así que se guardan una vez. Su consulta se usó una vez, para su propia fila de pesos, y se tira. Sus puntuaciones, su softmax y su paso por el MLP se hicieron cuando llegó. De todos esos triángulos y matrices de nnn filas, lo único que se mueve es una columna nueva, y casi todo lo que atraviesa (normalización, proyecciones, matriz de salida, MLP) le da la misma aritmética que tendría sola. Solo la atención lee otros tokens, y solo hacia atrás. La imagen sigue a una cabeza en ese momento con los tamaños de GPT-3; fíjate en la consulta que no se guarda, en la clave y el valor añadidos como filas nuevas, y en la tabla donde la consulta nueva se encuentra con cada clave guardada.
Una cabeza de atención mientras genera un token, con los tamaños reales de GPT-3, no los de Qwen. El residuo normalizado del token nuevo (1 × 12.288) pasa por las matrices de consulta, clave y valor de esta cabeza, de 12.288 × 128 cada una; su clave y su valor se añaden como filas nuevas de K y V, y su consulta se usa una vez y no se guarda. Por token, la tabla enseña la clave guardada, la puntuación (consulta por clave entre la raíz cuadrada de 128), el peso del softmax (los puntos suman 1) y el valor; la suma ponderada de los valores es la salida de la cabeza, 128 números. Números de juguete sobre una frase de 7 tokens.
Ese pasado guardado es la caché KV. Sin ella, cada token nuevo volvería a pasar la red sobre los 6.500 tokens anteriores para reconstruir sus claves, unos dos millones de pasadas de token para una respuesta de 300 tokens en lugar de 6.800. Guarda una clave y un valor por token, por capa de atención y por cabeza KV, cada uno calculado desde el residuo de esa capa (un token nuevo en la capa 39 lee lo que los anteriores habían llegado a ser en la capa 39), y nunca una consulta. En Qwen3.6 solo guardan una las diez capas de atención completa; las capas DeltaNet guardan su estado fijo. La caché es, en resumen, el diario del modelo de todo lo que tiene prohibido reconsiderar, que suena más sano que la mayoría de diarios. En la imagen, las líneas blancas son la inducción; la lente de memoria de la consola enseña la caché llenándose una columna por token.
La caché KV con los tamaños reales de GPT-3, no los de Qwen: una columna por token, una fila por capa, una clave y un valor por casilla. Las columnas antiguas están guardadas y no cambian; solo se calcula ahora la columna del token nuevo. Líneas blancas: un token en la capa 2 solo leyó los tokens hasta él mismo en la capa 1, así que la máscara causal fija su clave y su valor. Arcos: la consulta nueva lee las claves de su propia capa. Las claves y los valores se guardan por capa y por cabeza, a partir del residuo de esa capa y no del embedding; las consultas no se guardan. 96 capas × 96 cabezas dan 9.216 softmax por paso. Abajo: el prefill calcula la matriz triangular entera de una vez, cada fila con su softmax; el decode añade una fila por paso.
La memoria crece de forma lineal, una entrada más por capa y por cabeza KV por cada token, sin reescribir nada. El trabajo crece de forma cuadrática: la consulta del token ttt lee ttt claves, así que escribir nnn tokens cuesta n(n+1)/2n(n+1)/2n(n+1)/2 productos escalares por cabeza. Así que un paso de decode es, por cabeza, una fila de QKTQK^TQKT y una suma ponderada de VVV, y una columna a través de cada matriz de pesos de cada capa. La columna sigue cruzándose con cada peso activo, así que cada peso tiene que venir de memoria para un solo token, y por eso escribir espera a la memoria. Y la imagen de matrices enteras vuelve una vez por petición, cuando se lee el prompt. Los dos hechos dependen de dónde están físicamente los pesos.
El modelo en memoria
Hasta aquí los pesos eran números en fórmulas. Aquí pasan a ser bytes con dirección, en un archivo y después en la memoria de una máquina, y la distancia entre la memoria y el chip decide casi todos los números que vienen.
El archivo GGUF y la cuantización
En mi disco el modelo es un archivo. Ollama guarda qwen3.6:35b-a3b como unos cuantos blobs en ~/.ollama/models/blobs, y el más grande, de 21.718.480.960 bytes, es un archivo GGUF, el formato de llama.cpp. Cuarenta líneas de Python leen su estructura, y hacerlo una vez es la forma más rápida que conozco de dejar de tratar un modelo como un objeto embrujado.
Archivo GGUF, 21,7 GBun blob en el almacén de Ollama cabeceralos cuatro bytes GGUF, versión 3, 753 tensores, 36 claves de metadatos
metadatosarquitectura, capas, cabezas, contexto, tokenizador, plantilla de chat
tabla de tensoresnombre, forma, formato numérico y posición de cada tensor
datos de tensoreslos pesos, alineados, casi todo el tamaño
Los metadatos guardan todos los números usados hasta aquí:
TEXT Copy
general.architecture = qwen35moe qwen35moe.block_count = 41 # 40 layers + 1 for multi-token prediction qwen35moe.embedding_length = 2048 # width of the residual stream qwen35moe.attention.head_count = 16 # query heads qwen35moe.attention.head_count_kv = 2 # key-value sets (grouped-query attention) qwen35moe.attention.key_length = 256 qwen35moe.full_attention_interval = 4 # 1 layer in 4 is full attention qwen35moe.context_length = 262144 qwen35moe.expert_count = 256 qwen35moe.expert_used_count = 8 qwen35moe.expert_feed_forward_length = 512 tokenizer.ggml.merges = [247587 entries: 'Ġ Ġ', 'ĠĠ ĠĠ', 'i n', 'Ġ t', ...]
La tabla de tensores nombra cada matriz con su forma y su formato, así que leerla es leer la arquitectura: blk.0.attn_qkv es una proyección de Gated DeltaNet, blk.3.attn_q atención completa, blk.0.ffn_up_exps guarda 256 expertos en un solo tensor de 2048 × 512 × 256, y el router, blk.0.ffn_gate_inp, se queda en floats de 32 bits.
Las formas suman 35.505.251.456 parámetros, 71 GB a los dos bytes de un float de 16 bits. El archivo ocupa 21,7 GB por la columna de formatos. La cuantización guarda cada peso en menos bits, y lo primero que entendí mal es que no puede funcionar recortando decimales: 4 bits son dieciséis valores posibles, punto1. Coge un bloque de pesos (32, por ejemplo), busca una escala para el bloque y guarda cada peso como el índice del más cercano de los niveles equiespaciados que permite esa escala; leerlo de vuelta es nivel por escala. Esta es la segunda figura que he dejado interactiva, porque el error hay que verlo crecer según bajan los bits.
1Ni siquiera 16 bits son exactos: np.float16(0.1) guarda 0,0999755859375, el número más cercano que cabe en el formato.
stored at 4 bits (Q4_0)integers −8 to 7 + one fp16 scale bits per weight, scale included4.5 average error at 4 bitsabout 3% of the largest weight One block of 32 weights quantized: each weight is stored as a small integer and the block shares one fp16 scale, set by its largest weight; reading back is integer times scale. At 8, 4, 3 or 2 bits, with the error of each weight.
Con 8 bits el error no se ve; con 4 bits, dieciséis enteros de −8 a 7, es de media un 3 % del mayor peso del bloque, que es como ejecuta los modelos casi todo el mundo; con 2 bits llega al 12 %. El único peso grande obliga a una escala grande y deja a los pequeños con pocos niveles cerca de cero, cosa que los formatos K suavizan con un segundo nivel de escalas más pequeñas. Cada bloque guarda además su escala, así que los bits por peso nunca son redondos: Q8_0 son 8,5. El archivo de Qwen3.6 mezcla Q4_K a 4,5 bits y Q6_K a 6,56 para las matrices que más sufren al redondear, y sale a 4,89 de media, que es lo que significa Q4_K_M en la práctica. El tamaño de un modelo es siempre parámetros por bits por peso entre ocho: 35.500 millones por 4,89 entre 8, 21,7 GB. El 7B denso que usan los cursos de GPU, que tomo prestado más abajo para una tarjeta de centro de datos, ocupa 28 GB en fp32 (cuatro bytes por peso), 14 GB en fp16 o bf16, 7 GB en fp8 y 3,5 GB en 4 bits.
Parámetros y gigabytes son números distintos.
Una diapositiva que me hice decía que el modelo denso de 27B lee 17 GB por token, y lo tomé por un error. Las dos cosas son ciertas: 27.300 millones de parámetros a 4,9 bits son 16,8 GB. Mis apuntes tenían el error contrario, un modelo de 20B a 17 GB en Q4; en Q4_K_M son unos 12 GB.
Nada del archivo es ejecutable: el motor pone la aritmética y el archivo los números y las formas, y por eso el artículo anterior pudo enlazar un mismo archivo entre Ollama y LM Studio.
Ancho de banda y escalera de memoria
La capacidad decide si un modelo cabe y el ancho de banda a qué velocidad escribe, porque el chip solo tiene unos pocos megabytes de memoria rápida y cada peso con el que se cruza la columna en movimiento atraviesa el bus hasta él, en cada token. Los dos modelos que tengo separan las dos cosas: el MoE ocupa más (21,7 GB) y lee mucho menos por token, unos 1,84 GB (sus 3.000 millones de pesos activos más las partes por las que pasa todo token), mientras que el denso de 27B ocupa menos y lo lee casi entero, unos 16 GB: nueve a uno.
La LPDDR5X-8000 hace 8.000 millones de transferencias por segundo en un bus de 128 bits, 16 bytes cada una: 128 GB/s. Los portátiles finos se quedan en 128 bits porque el ancho cuesta pines y consumo; el M4 Max de Apple usa 512 bits para 546 GB/s1. Una tarjeta de sobremesa tiene su propia VRAM en un bus mucho más ancho, 32 GB a 1.792 GB/s en una RTX 50902; un modelo que no cabe en ella se reparte con la CPU, y la parte lenta marca el ritmo (ollama ps enseña el reparto). La iGPU del portátil usa en cambio la RAM del sistema, grande y lenta, un cambio razonable cuando una tarjeta de 16 GB no puede ni cargar este modelo. Tiene 8 unidades de cálculo de 64 carriles cada una; Ollama no usa la NPU.
1Apple, New MacBook Pro features M4 family of chips, 30 de octubre de 2024: M4 Max con hasta 546 GB/s de ancho de banda de memoria. 2NVIDIA, Compare GeForce graphics cards: RTX 5090, 32 GB GDDR7, 1.792 GB/s; RTX 5080, 16 GB, 960 GB/s.
Cada una de estas máquinas es una escalera de memorias, cada peldaño más grande y más lento que el de debajo (jerarquía de memoria). La del portátil es corta, disco, RAM compartida y unos pocos megabytes en el chip, y su iGPU lee la misma RAM en la que se cargó el modelo. La H100 es la tarjeta cuyos peldaños están documentados1, así que desde aquí hasta la caché KV sigo el ejemplo que usan todos los cursos de GPU junto a nuestro modelo: un 7B denso en fp16, 14 GB, 32 capas de ancho 4.096, en una H100 de un servidor.
1NVIDIA, NVIDIA Hopper Architecture In-Depth, 2022: H100 SXM5 con 132 SM, cuatro tensor cores y 128 núcleos FP32 por SM, hasta 228 KB de memoria compartida por SM y 50 MB de L2; la página de la H100 da 80 GB a 3,35 TB/s y 1.979 TFLOP/s en BF16 con dispersión, unos 990 en denso.
SSD NVMe · terabytes7 GB/s · 2 s · al cargarRAM del servidor · 64 a 512 GBPCIe 5.0 · 64 GB/s · 220 ms · al cargarHBM3 (VRAM) · 80 GB · aquí viven los pesosbus de memoria · 3,35 TB/s · 4,2 ms · cada pasocaché L2 · 50 MBen el chip · nunca cabe el modelo enteroSM · 228 KB × 132modelo cargado Un servidor con una H100 SXM; cada tiempo es lo que tarda en moverse el 7B entero en fp16, 14 GB. Más abajo, más rápido y más pequeño.
Un modelo está cargado cuando está en la HBM. Los dos peldaños de arriba se pagan una vez: el SSD pasa los 14 GB a la RAM en unos 2 s y el PCIe 5.0 los lleva a la tarjeta en unos 220 ms, y la línea entre la RAM y la HBM es donde un archivo se convierte en un modelo en servicio. La HBM es la memoria y los 3,35 TB/s son el bus que tiene al lado, que cada peso cruza en cada paso: 14 GB en 4,2 ms. Dejar los pesos en la RAM y traerlos por PCIe en cada paso (offloading) costaría 220 ms por token en vez de 4,2, unas cincuenta veces más lento. Los dos peldaños de abajo no llegan a tener nunca el modelo entero, cosa que me costó un tiempo vergonzoso digerir.
Prefill y decode en el hardware
Este es el hecho del que cuelga el resto del artículo, y el que me habría gustado que alguien me contara primero: a las matrices de pesos les da igual cuántos tokens vengan. El dominio de toda esta parte es un paso (una pasada por las 32 capas) del 7B denso en fp16 en una H100 SXM, con máximos de ficha técnica (los sistemas reales sacan del 60 al 80%); el portátil vuelve al final de cada sección.
La imagen en la que acabaron mis fichas de Anki es una cocina. Los pesos son los ingredientes, 14 GB, guardados en la despensa (la HBM). Los cocineros son los 132 streaming multiprocessors (SM). Entre unos y otros, un pinche, el bus de memoria, trae los ingredientes caja a caja (un bloque de pesos), y un paso del modelo es una ronda completa: cada caja de la despensa traída una vez, BAM BAM BAM, 14 GB a 3,35 TB/s, 4,2 ms. Un token es un plato. La caja cuesta lo mismo de traer acabe en los platos que acabe, así que con un token o con 2.048 la ronda del pinche dura 4,2 ms, y lo que cambia con el número de platos es cuánto trabajan los cocineros con cada caja.
Los pesos, bloque a bloque
Cada SM tiene cuatro tensor cores (unidades que multiplican un bloque pequeño de una matriz por otro y acumulan, en una sola instrucción) junto a 128 carriles normales para el trabajo elemento a elemento (softmax, normalización, activaciones), y una encimera de 228 KB de memoria compartida; los 132 hacen unos 990 billones de operaciones por segundo. Nada de lo que forma un modelo cabe en esa encimera. Una capa del 7B son unos 400 MB, su WQW_QWQ (4.096 × 4.096) 33 MB, la parte de una cabeza (4.096 × 128) 1 MB, y solo cabe un bloque de 128 × 128 números, 32 KB, con sitio para la entrada y las sumas acumuladas.
Así que cada matriz se trocea en bloques (tiling): un SM pone un bloque en su encimera, lo multiplica por los tokens que tiene, suma el resultado a sus acumuladores y lo suelta, mientras el siguiente ya viene de camino. Esto lo escriben las bibliotecas de kernels (cuBLAS, CUTLASS, Triton); el código del modelo solo dice x @ W. El modelo nunca está entero en el chip (las 132 encimeras suman 30 MB, el 0,2%). Los 4,2 ms son el chorro acumulado de cada peso traído una vez, algo más en la práctica porque algunos bloques se leen dos veces y también viajan las activaciones y la caché KV, y son además el suelo de la latencia del decode: ningún token puede escribirse más rápido que una ronda de la despensa.
Ahora la misma caja en las dos fases. En el decode el plato es un token, un vector escuálido de 4.096 números, así que cada bloque de 32 KB sirve a ese único token: 128 × 128 multiplicaciones con suma, unas 32.000 operaciones. En el prefill el bloque se encuentra con una matriz gorda de tokens del prompt, y con 128 de ellos da unos 4 millones, por exactamente el mismo viaje.
decodeW128 × 12832 KB×1 tokenvector escuálido32K operacionesprefillW128 × 12832 KB×128 tokensmatriz gorda4M operaciones Un bloque de una matriz de pesos en un SM de una H100. Traer el bloque a la memoria compartida cuesta lo mismo; cambia cuántos tokens lo usan antes de soltarlo.
Imagina al vector escuálido al final del bus mientras le echan encima 14 GB de pesos, 32 KB cada vez, cada bloque usado una vez y tirado. Es la forma más cara jamás inventada de calcular 4.096 números, y es como escribe cualquier chatbot.
Dentro del paso, cada capa se ejecuta como ocho etapas en orden, cada una una orden a la GPU (un kernel), y dentro de una etapa todo va en paralelo: cada token, cada cabeza, cada número del resultado.
1Q, K, V (WQ, WK, WV juntas)lee pesos2Q · Kᵀ (puntuaciones)calcula · KV*3máscara causal y softmaxcalcula4pesos · Vcalcula · KV*5· WO y suma residuallee pesos6LayerNormcalcula7MLP: · W1 y activaciónlee pesos8MLP: · W2 y suma residuallee pesosFlashAttention:un solo kernel* en el decode, 2 y 4 leen además la caché KV de la HBM
Una capa (pre-norm), que se repite en las 32. Cada etapa es un kernel y las etapas van en fila, con todo lo de dentro de cada una a la vez.
Las cuatro etapas que multiplican por una matriz de pesos se llevan casi todo el tráfico; las otras cuatro solo calculan con lo que ya tienen (un modelo pre-norm como GPT-3 o Qwen ejecuta una normalización más antes de la primera). En el decode, las etapas 2 y 4 leen además la caché KV, el pasado congelado de la columna en movimiento. Entre etapas y entre capas el orden es estricto, porque cada una necesita la salida de la anterior. FlashAttention junta las etapas 2 a 4 en un solo kernel (fusión de kernels), así que las n×nn \times nn×n puntuaciones nacen y mueren en la memoria compartida sin escribirse nunca en la HBM, y un softmax acumulado mantiene el resultado exacto1.
1Dao et al., FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness, 2022.
Duración del paso y punto de equilibrio
El pinche y los cocineros trabajan a la vez con cajas distintas: mientras los tensor cores multiplican el bloque kkk, el bus ya trae el k+1k+1k+1 (doble búfer; la H100 tiene una unidad que copia de forma asíncrona). Eso es solapamiento, un paralelismo distinto del de los 132 cocineros repartiéndose una matriz: repartir divide la aritmética, solapar esconde el tiempo del rápido bajo el del lento.
decode, 1 token: los SM esperan al busbusSM12345paso ≈ la suma de los viajes (4,2 ms)prefill, 2.048 tokens: el bus esperabusSM12345paso ≈ la suma de los cálculos (29 ms)bus: trae kSM: calculan kespera
Una matriz de pesos en una H100, bloque a bloque, cada fila a escala. Dos unidades distintas trabajan a la vez: el bus copia, los tensor cores multiplican, y el rápido queda escondido bajo el lento.
Así que un paso dura lo que el más lento de los dos, tpaso=max(tmemoria,tcaˊlculo)t_{\text{paso}} = \max(t_{\text{memoria}}, t_{\text{cálculo}})tpaso=max(tmemoria,tcaˊlculo), nunca la suma. La ronda del pinche es fija, 4,2 ms; los cocineros gastan dos operaciones por peso en cada plato, 14.000 millones de operaciones, 0,014 ms por token a 990 TFLOP/s. A escala de cocina, es un pinche que tarda 300 segundos en traer la caja y un cocinero que tarda un segundo por plato. Tres pasos en un mismo eje de tiempo:
1 token (decode)4,2 ms por tokenbus4,2 msSM0,014 ms · luego esperapaso: 4,2 ms para 1 token295 tokens (equilibrio)0,014 ms por tokenbus4,2 msSM4,1 mspaso: 4,2 ms para 295 tokens2.048 tokens (prefill)0,014 ms por tokenbus4,2 ms · luego esperaSM29 mspaso: 29 ms para 2.048 tokensbus: traer pesosSM: calcularesperando
Un paso de un 7B en fp16 por sus 32 capas en una H100, los tres en el mismo eje de tiempo. El bus trae pesos mientras los SM calculan, y el paso acaba cuando acaba el más lento.
Un plato por caja, un paso de decode: la ronda dura 4,2 ms y los cocineros trabajan 0,014 ms, parados el 99,7% del paso, para unos 240 tokens por segundo.
Unos 300 platos (295 tokens): los dos tardan 4,2 ms y nadie espera.
2.048 platos, un paso de prefill: el pinche acaba a los 4,2 ms y espera mientras los cocineros trabajan 29 ms, para unos 70.000 tokens por segundo.
El tercer caso es el momento en que, si pegas la oreja al servidor, estoy bastante seguro de que se oye a la matriz gritando fuck yeah, colega: por una vez cada caja que cruza el bus tiene una multitud de tokens esperándola y cada tensor core tiene trabajo. Por token, el paso de decode cuesta unas 300 veces más.
El cruce es el punto de equilibrio. La memoria es una línea plana en 4,2 ms, el cálculo una pendiente de 0,014 ms por token, y se encuentran en 4,2/0,014≈2954{,}2 / 0{,}014 \approx 2954,2/0,014≈295 tokens: por debajo, el paso está limitado por memoria y su duración no depende de los tokens; por encima, está limitado por cálculo y crece 0,014 ms por token.
0100200300400500600tokens en el paso02468msmanda la memoriamanda el cálculo≈ 295 tokensmemoria: 4,2 ms fijos(14 GB ÷ 3,35 TB/s)cálculo: 0,014 mspor tokenlínea gruesa: lo que dura el paso (el mayor de los dos)
Un paso de un 7B en fp16 por sus 32 capas en una H100. El cruce es 990 ÷ 3,35, así que el tamaño del modelo se cancela.
Escribe el cruce entero y el 14 se cancela: 14 GB / 3,35 TB/s14×109 ops / 990 TFLOP/s=9903,35≈295\frac{14\ \text{GB} ,/, 3{,}35\ \text{TB/s}}{14 \times 10^{9}\ \text{ops} ,/, 990\ \text{TFLOP/s}} = \frac{990}{3{,}35} \approx 29514×109 ops/990 TFLOP/s14 GB/3,35 TB/s=3,35990≈295. El tamaño del modelo está arriba y abajo de la raya, así que el punto de equilibrio es de la GPU, no del modelo: un 70B en fp16 en la misma tarjeta (si cupiera) se equilibra en los mismos 295. En fp16 un peso son 2 bytes y hace 2 operaciones por token, así que las operaciones por byte leído, la intensidad aritmética, son exactamente los tokens del paso. Lo que mueve el punto es otra GPU u otro formato: en 4 bits, medio byte por peso, baja a unos 74 tokens.
Nada de esa cuenta mencionaba capas ni orden, solo los totales del paso, y aun así dio el tiempo correcto. Ese es el nivel de recuento, un análisis roofline, que da una cota; el nivel de ejecución es lo que corre el chip, 32 capas en fila de kernels y bloques. Aquí coinciden porque cada capa del 7B tiene la misma proporción de bytes y operaciones; en general, el tiempo real es la suma del lado más lento de cada capa, nunca por debajo de la cota, más el coste de lanzar kernels.
nivel de recuento: totales del paso, sin ordenbytes14 GB ÷ 3,35 TB/s = 4,2 msops2.048 × 14B ops ÷ 990T/s = 29 mspaso = max(4,2; 29) = 29 msnivel de ejecución: 32 capas en filabytesopscapa 1capa 32cada capa: max(0,13; 0,9) ms · 32 × 0,9 = 29 ms Un paso de prefill de 2.048 tokens por un 7B en fp16 en una H100. Los dos niveles dan lo mismo porque todas las capas tienen la misma proporción; si no la tuvieran, el recuento se quedaría corto.
Las dos fases hacen la misma aritmética por token con cuellos de botella opuestos, el diagnóstico más útil que conozco en cualquier máquina:
Prefill
Decode
el prompt entero, o un trozo Tokens por paso uno por conversación
todos los tokens del paso Cada bloque de pesos sirve a un token
la aritmética (tensor cores) Lo limita el ancho de banda de memoria
2.048 tokens en 29 ms 7B fp16 en una H100 4,2 ms por token
tiempo hasta el primer token (TTFT) Se nota como tokens por segundo después
prompt eval rate Ollama lo llama eval rate
93 tok/s En el portátil, Qwen3.6 11,7 tok/s
El decode en la práctica
El primer token de la respuesta existe y el segundo depende de él, así que el decode no puede escaparse de una ronda de la despensa por token, un paso de decode con una sola columna. Cada peso activo cruza el bus una vez por token, y eso pone un techo:
tok/s≤ancho de bandabytes leıˊdos por token\text{tok/s} \le \frac{\text{ancho de banda}}{\text{bytes leídos por token}}tok/s≤bytes leıˊdos por tokenancho de banda
El doble de modelo, la mitad de velocidad. En la H100 son 3.350 / 14, unos 240 tokens por segundo para el 7B; un 13B da unos 129, un 70B unos 24 si cupiera. En el portátil, 1,84 GB por token de 128 GB/s dan unos 70 tokens por segundo, y el motor llega a 11,7, medidos. Las ejecuciones reales nunca tocan el techo (la memoria de este portátil entrega del 30 al 40% de su ancho de banda teórico con este patrón de acceso, y cada una de las 40 capas añade un coste fijo por lanzar trabajo a la GPU), pero el techo explica la proporción entre modelos. El denso de 27B lee unos 16 GB por token, un techo de 8 tokens por segundo, y midió 2,6: ocho veces los bytes, unas cuatro veces y media más lento, que es todo el argumento a favor de las mezclas de expertos en una máquina así. El pensamiento de un modelo de razonamiento también es decode, una columna por token pensado, así que los 20.000 tokens de pensamiento de un problema difícil le cuestan al 7B en una H100 unos 83 segundos antes de que empiece la respuesta.
Otras máquinas se acercan más. En las 67 ejecuciones publicadas de llama-bench con modelos densos sobre las que está ajustado el modelo de tiempos de TokenWalk, de un M1 a una MI300X, un solo flujo usa una mediana del 74% del ancho de banda de la ficha técnica; la ejecución densa del portátil usa el 34%, y el MoE el 17%, porque sus capas leen tan poco que el coste fijo pesa más. Una Strix Halo, también iGPU de AMD, llega al 77 a 81% con llama.cpp, así que cuánto de la diferencia es Ollama en Windows no lo he medido.
Parte del ancho de banda especificado que usa un solo flujo de decode, calculada como tokens por segundo por bytes leídos por token entre los GB/s de la ficha técnica. En gris, 67 ejecuciones publicadas de generación con llama-bench sobre modelos densos (tg desde un contexto vacío), de data/benchmarks.json de TokenWalk, con los anchos de banda de data/machines.json y data/bench-hardware.json; los bytes por token son el tamaño del archivo, y las filas MoE quedan fuera porque en ellas no lo son. En azul, las medianas de Ollama en este portátil del benchmark de apu-llm, 11,7 y 2,6 tok/s, con los bytes por token de los dos archivos GGUF.
Por eso importa la cuantización aunque el modelo quepa: reducir los bytes a la mitad casi duplica la velocidad de escritura, mientras que la memoria que sobra no vale nada. La cocina del portátil tiene un punto de equilibrio más bajo que la de la H100, unas 18 operaciones por byte, y un paso de decode allí hace unas 3,3 (dos por peso, a 0,61 bytes por peso), así que sus cocineros también se pasan casi todo el paso esperando.
Una conversación solo puede meter un plato en cada caja. Un servidor llena la caja con platos de otra gente (batching): 64 usuarios en decode juntos comparten un viaje de los pesos, el paso sigue durando 4,2 ms con su aritmética en solo 0,9 ms, y la tarjeta escribe unos 15.000 tokens por segundo con los tensor cores ocupados el 22% del tiempo. Cada uno de esos usuarios sigue esperando 4,2 ms por token, los mismos 240 tokens por segundo que solo, así que el batching sube lo que entrega el servidor y deja la velocidad de cada usuario donde estaba. Hasta unos 295 usuarios el paso no se alarga, salvo por la caché KV de cada usuario.
El prefill en la práctica
Ahora el minuto de silencio. El prefill existe porque al principio de una petición el prompt entero se conoce y la caché está vacía, así que la imagen de matrices enteras vuelve una vez: todos los tokens del prompt atraviesan las capas juntos, como en el entrenamiento pero sin la pasada hacia atrás, con la máscara dejando a cada uno ciego a los siguientes, y cada capa es una matriz por una matriz. Deja en la caché las claves y los valores de cada token y el residuo final del último, del que sale la primera palabra. En el portátil, Ollama lo alimenta en trozos de 512 tokens, muy por encima del punto de equilibrio de la iGPU (unas 18 operaciones por byte, que se alcanzan con menos de diez tokens por lectura). Qwen3.6 necesita unos 6.000 millones de operaciones por token, 6.500 tokens son unos 39 billones, y la GPU del portátil saca de 0,6 a 0,75 billones de operaciones útiles por segundo en este trabajo: algo más de un minuto. Una tarjeta de sobremesa ayuda aquí mucho más que en el decode, porque la diferencia en unidades de cálculo (8 frente a 170) es mucho mayor que la de ancho de banda.
El prefill es un solo paso, y aun así crece con el prompt, cosa que la imagen del paso puede esconder. Con el prompt entero en un paso, la parte de los pesos es lineal (29 ms para 2.048 tokens en la H100, 450 ms para 32.000), y las puntuaciones de la atención crecen con el cuadrado del prompt: un 4% más con 2.048 tokens y un 60% con 32.000, unos 0,7 s en total1. En el ejemplo del 7B con un prompt de 2.048 tokens y una respuesta de 500, el prefill tarda 29 ms y la respuesta 2,1 s, el 98% del tiempo: el prefill trae las cajas una vez para todos sus tokens, el decode una vez por token.
1Kaplan et al., Scaling Laws for Neural Language Models, 2020, sección 2.1: el término de la atención queda por debajo del resto de la pasada mientras el contexto sea menor que unas doce veces el ancho del modelo.
Un arranque lento es prefill, y las palancas están en el prompt: menos pasajes y mejores, un resumen en vez de la historia entera, un principio fijo que el motor pueda reutilizar. Una escritura lenta es decode, y la palanca son los bytes por token: un formato más pequeño, menos parámetros activos o un bus más ancho.
Chunked prefill
Un servidor (vLLM, SGLang, TensorRT-LLM) no mete un prompt de 32.000 tokens en un solo paso. Lo trocea en trozos de, por ejemplo, 512 a 2.048 tokens y ejecuta un trozo por paso; cada trozo escribe sus claves y valores en la caché, y el último da el primer token de la respuesta. El resultado es idéntico a hacerlo de golpe gracias a la máscara causal: los tokens del trozo 2 atienden a sí mismos y a las claves y valores del trozo 1 leídos de la caché, y un token que mira hacia atrás no distingue si lo que tiene detrás se acaba de calcular o venía de la caché (el único coste extra es releer las claves y valores de los trozos anteriores). Es una decisión del servidor (el tiling, en cambio, ocurre dentro de cada paso), y hay tres motivos:
Un prompt de 32.000 tokens en un paso ocupa la GPU casi medio segundo, y cada usuario que estaba a mitad de respuesta se queda parado.
Un paso de decode deja a los cocineros ociosos mientras las cajas llegan igual, y un trozo de prefill puede ir en esa misma ronda (piggybacking).
Los resultados intermedios crecen con los tokens del paso, y los de la atención con su cuadrado, así que los trozos acotan el pico de memoria.
El viaje solo sale gratis hasta llenar el hueco. Cincuenta usuarios en decode tardan 4,2 ms con los SM ocupados 0,7 ms, así que caben unos 245 tokens más antes de que el paso llegue a los 295 del equilibrio y pase a mandar el cálculo.
50 usuarios en decode4,2 ms · SM ocupados 17 %busSM50 + 245 tokens de prompt4,2 ms · el prefill va gratisbusSM50 + 1.000 tokens de prompt≈ 15 ms · los 50 también esperanbusSMlo mismo en dos pasos separados4,2 + 14 = 18,2 ms · dos viajesbusSMbus: pesosSM: 50 de decodeSM: prefillespera
Un paso de un 7B en fp16 por sus 32 capas en una H100, los cuatro en el mismo eje de tiempo. El prompt va gratis hasta el punto de equilibrio, 295 tokens en el paso; pasado ese punto, el paso crece 0,014 ms por token para todos.
Mete 1.000 tokens de prompt en ese paso y dura 1.050×0,014≈151{.}050 \times 0{,}014 \approx 151.050×0,014≈15 ms: sigue ahorrando una ronda de la despensa frente a un paso aparte (4,2 + 14 = 18,2 ms), pero los cincuenta usuarios en decode lo pagan con un paso de 15 ms en vez de 4,2, un tirón visible en su texto, así que los trozos que se mezclan con el decode son moderados. Los trozos de más de unos 300 tokens tampoco aportan velocidad: 32.000 tokens en 16 trozos de 2.000 son 16 rondas (67 ms) escondidas bajo 450 ms de aritmética, lo mismo que de golpe.
La caché KV en bytes
Los pesos no son lo único que carga el pinche. En el decode, la caché KV de cada conversación es un segundo saco, que se lee entero en cada paso por el mismo bus, así que su tamaño también es una cuestión de velocidad:
MKV=2×ncapas con KV×ncabezas kv×dcabeza×b×ntokensM_{KV} = 2 \times n_{\text{capas con KV}} \times n_{\text{cabezas kv}} \times d_{\text{cabeza}} \times b \times n_{\text{tokens}}MKV=2×ncapas con KV×ncabezas kv×dcabeza×b×ntokens
En Qwen3.6 con números de 16 bits eso da 2×10×2×256×2=20.4802 \times 10 \times 2 \times 256 \times 2 = 20{.}4802×10×2×256×2=20.480 bytes, 20 KB por token: la grouped-query attention la dividió entre ocho, y guardarla en diez capas de cuarenta, entre cuatro. El 7B sin más del ejemplo de la H100, 32 capas con claves y valores para todas las cabezas en un ancho de 4.096, guarda 2×32×4.096×22 \times 32 \times 4{.}096 \times 22×32×4.096×2 bytes, 0,5 MB por token, veinticinco veces más, y cada paso de decode mueve los pesos más toda la caché.
tok/s0 tok14 GB por paso2392.500 tok14 + 1,3 GB por paso2198.000 tok14 + 4,2 GB por paso18416.000 tok14 + 8,4 GB por paso15032.000 tok14 + 16,8 GB por paso109pesos, 14 GBcaché KV, 0,5 MB/token
Un paso de decode para un usuario de un 7B en fp16 sin GQA, en una H100. Cada paso de decode lee los pesos y la caché entera por el mismo bus, así que la velocidad es 3.350 GB/s dividido entre los bytes por paso.
Al final de un prompt de 2.000 tokens con una respuesta de 500, el paso mueve 15,3 GB y escribe unos 219 tokens por segundo en vez de 240. Con 32.000 tokens el segundo saco pesa unos 17 GB, más que el modelo, el paso mueve el doble de bytes y la velocidad baja a la mitad, unos 110 tokens por segundo. La grouped-query attention con 8 cabezas KV, como en Mistral 7B, dividiría la caché entre cuatro, y el batching no hace nada por ella, porque cada conversación trae su propio saco.
Ejecución en el portátil
Vuelta a la mesa, donde la memoria se comparte con Chrome y cada ajuste es mío para equivocarme. La física es la de la H100 con números más pequeños, y el motor toma decisiones por ti.
Reserva de contexto y presupuesto de memoria
La caché se reserva al cargar para todo el contexto que pide la petición (num_ctx), se use o no, y Ollama fija ese valor por defecto según la memoria que detecta: aquí eligió 262.144 tokens, 5,4 GB reservados para una conversación de 7.000 tokens, cuatro veces eso en un modelo sin el truco híbrido. Poner OLLAMA_CONTEXT_LENGTH a lo que necesita la aplicación es el primer cambio que haría (la consola con la reserva completa enseña lo que retiene). Antes de cargar ningún modelo, Windows con Chrome, Cursor, Claude Code y Anki ya usaba 32 GB, y Chrome, como siempre, se lo estaba pasando en grande.
num_ctx = 16.384 (a mano)32 GB21,7 GBKV 336 MBlibre ≈ 9,8 GBnum_ctx = 262.144 (lo eligió Ollama)32 GB21,7 GBKV 5,4 GBlibre ≈ 4,8 GBsistema y appspesoscaché KVlibre
Cada barra son los 64 GB del portátil, a escala, con Qwen3.6 en Q4_K_M y su caché en f16. La caché se reserva para todo el num_ctx, se use o no; sin las capas híbridas ocuparía 4 × 5,4 = 21,5 GB.
Con Qwen3.6 y 16.384 tokens de contexto quedan unos 10 GB libres, más o menos lo que el sistema necesita para respirar; con 64 GB el problema es la velocidad, nunca que quepa. La caché se puede guardar en menos bits, aparte de los pesos (cuantización de la caché KV, OLLAMA_KV_CACHE_TYPE=q8_0, más o menos la mitad de memoria): cuantizar los pesos redondea el modelo, cuantizar la caché redondea la historia del paciente tal como el modelo la ha leído. Ollama exige el kernel fusionado de FlashAttention (OLLAMA_FLASH_ATTENTION=1) para cuantizar la caché.
Trucos para acelerar el decode
Cada truco para escribir más rápido cambia trabajo barato o memoria barata por lecturas caras, y cada uno tiene una condición en la que deja de compensar. Tres deciden cómo se siente este portátil: comprobar varios tokens adivinados por cada lectura de los pesos, no leer dos veces un prompt, y meter menos texto en el prompt de entrada.
Speculative decodingPrefix cachingRecuperación
El speculative decoding le da al decode algo del reaprovechamiento del prefill: un modelo borrador pequeño con el mismo tokenizador (Qwen3 0.6B para un Qwen) escribe unos cuantos tokens por delante, y el grande los comprueba todos en una pasada, un prefill diminuto que cuesta más o menos un paso normal. Es un becario que redacta y un jefe que firma, y el jefe firma desde la izquierda hasta el primer token en el que no está de acuerdo, donde pone el suyo. Una regla de rechazo mantiene la distribución de salida exactamente igual a la del modelo grande1, así que solo cambia la velocidad, que depende de la tasa de aceptación α\alphaα. Con γ=4\gamma = 4γ=4 tokens de borrador, el primero de una ronda siempre sale, el segundo necesita una aceptación, el tercero dos seguidas, y si pasan los cuatro la misma pasada da un quinto:
1Leviathan, Kalman y Matias, Fast Inference from Transformers via Speculative Decoding, ICML 2023, que demuestra que la distribución de salida se conserva y deriva la aceleración esperada.
E[tokens por ronda]=1−αγ+11−αE[\text{tokens por ronda}] = \frac{1 - \alpha^{\gamma + 1}}{1 - \alpha}E[tokens por ronda]=1−α1−αγ+1
Con α=0,7\alpha = 0{,}7α=0,7 una ronda da 2,8 tokens por una pasada del modelo grande. Para nuestros dos modelos el veredicto sale opuesto, que es lo que más me interesó. El denso de 27B lee 16 GB una vez compruebe un token o cinco, y cuatro pasos de un borrador de 0,6B son baratos al lado, así que una ronda va más o menos el doble de rápido. El paso normal del MoE solo lee 1,84 GB, mientras que comprobar cinco tokens despierta hasta cuarenta expertos por capa en vez de ocho, así que la comprobación lee varias veces más y el borrador cuesta más de lo que ahorra. Eso sale de costes de primer orden; medirlo es el experimento pendiente (denso con borrador, MoE con el mismo borrador).
El archivo de Qwen3.6 trae un borrador incorporado, un bloque 41 de predicción multitoken que Ollama ejecuta con dos borradores por ronda, y en este portátil solo compensa cerca del muestreo voraz: a temperatura 0 subió la escritura de 13,2 a 15,6 tokens por segundo, y a 0,7 costó tiempo (11,5 y 10,6 frente a 13,2), porque una conjetura más barata se rechaza más a menudo.
Un modelo no recuerda nada entre peticiones. Un chat parece continuo porque la aplicación reenvía la conversación entera cada vez, la ventana de contexto de la llamada, y cada llamada paga prefill sobre todo ello salvo que el motor haya visto ya el principio. Eso es el prefix caching: la llamada 4 empieza con los mismos tokens que la 3, así que por la máscara causal sus claves y valores son idénticos, y un motor que los guardó solo calcula la cola nueva. La coincidencia tiene que ser exacta desde el primer token. Cambia una palabra del prompt de sistema y cada token posterior la atendió, así que la llamada 4 recalcula los 7.600 tokens y vuelve a pagar el minuto entero; lo estable va delante y la pregunta variable al final.
Anthropic cobra 1,25 veces el precio de entrada por escribir un prefijo en su caché de cinco minutos y una décima parte o menos por leerlo1. Ollama guarda la caché de la última petición en el modelo cargado, cosa que descubrí por casualidad cuando una prueba reutilizó un principio de prompt y leyó 2.900 tokens a entre 7.000 y 9.000 tokens por segundo. El servidor de llama.cpp también puede guardar la caché de un slot en disco (--slot-save-path): la historia de 6.500 tokens cuesta unos 70 segundos de prefill y 133 MB de caché, que un SSD lee en una décima de segundo, así que un copiloto que restaura la caché de cada paciente responde en más o menos un segundo (en un modelo híbrido hay que guardar también el estado DeltaNet, cosa que no he probado).
1Anthropic, Prompt caching, precios y el orden de invalidación herramientas, sistema, mensajes.
Casi todos los prompts largos salen de la generación aumentada por recuperación (RAG): se buscan los pasajes relevantes para la pregunta y se pegan en el prompt, normalmente con búsqueda híbrida, que fusiona BM25 (palabras exactas, bueno con nombres de fármacos, ciego a los sinónimos) y un modelo de embeddings (encuentra anticoagulante oral para warfarina, puede perderse un código exacto). La recuperación decide cuánto texto entra en el prefill: a unos 90 tokens por segundo, diez pasajes de 500 tokens añaden casi un minuto antes de la primera palabra, así que menos pasajes y mejores son la aceleración más barata que tiene un sistema RAG.
El portátil ejecuta un modelo de 35B en privado y gratis a más o menos velocidad de lectura, y es la máquina equivocada para tiradas largas de aritmética (una evaluación sobre miles de casos, un ajuste fino). Para eso alquilo una RTX 5090, 0,69 dólares la hora en la nube comunitaria de RunPod en octubre de 20261, como hice para el ajuste fino de Stable Diffusion.
1RunPod, Pricing, página actualizada el 8 de octubre de 2026.
Ollama y el ciclo de vida del modelo
El propio modelo puede salir de la memoria. Ollama es un servidor en segundo plano en localhost:11434, del que el comando ollama es un cliente más, y con él un modelo pasa por cinco estados.
1Pulluna vez, tiempo de red2Load55 s en frío, del disco a la RAM3Serveprefill y decode4Idleocupa memoria5Unloadtras keep_alive
El keep-alive decide el último paso, cinco minutos por defecto, y la primera petición después de descargar paga la carga entera otra vez: pregunta cada tres minutos y el modelo siempre está ahí, pregunta cada diez y cada pregunta espera 55 segundos antes de que empiece siquiera el prefill (un arranque en frío en la consola). Estos son los ajustes que necesitó el portátil, más uno sin el que la GPU ni se usaba:
OLLAMA_IGPU_ENABLE1sin él, Ollama ignora la 860M y va por CPU OLLAMA_CONTEXT_LENGTH16384num_ctx por defecto; evita la reserva de 262.144 tokens OLLAMA_FLASH_ATTENTION1atención por bloques, necesaria para cuantizar la caché OLLAMA_KV_CACHE_TYPEq8_0caché a más o menos la mitad de memoria OLLAMA_KEEP_ALIVE30mevita recargar entre ratos de uso
El servidor los lee al arrancar, así que necesitan reiniciarlo (setx y reiniciar desde la bandeja en Windows, systemctl edit ollama en Linux; un export en una terminal no le hace nada a un servidor en marcha). Las opciones que van en una petición los sustituyen para esa llamada, y cada respuesta devuelve los tres números de los que trata este artículo:
PYTHON Copy
import requests r = requests.post("http://localhost:11434/api/generate", json={ "model": "qwen3.6:35b-a3b", "prompt": open("history.txt").read(), "stream": False, "options": {"num_ctx": 16384}, }).json() s = lambda k: r[k] / 1e9 # durations come in nanoseconds print("load", s("load_duration")) print("prefill tok/s", r["prompt_eval_count"] / s("prompt_eval_duration")) print("decode tok/s", r["eval_count"] / s("eval_duration"))
Ollama esconde las opciones del motor y te gestiona archivos y memoria; el servidor de llama.cpp lo expone todo (un modelo borrador con --model-draft, cachés en disco) y no gestiona nada; LM Studio queda en medio.
Números y respuestas
Casi todas las preguntas sobre si un modelo va a funcionar, y a qué velocidad, se pueden responder antes de descargar nada, y el resto de las preguntas que tenía tras dejar la clase caben en una tabla.
Cuentas de servilleta
Tamaño GB de un modeloparámetros en miles de millones × bits por peso ÷ 835,5 × 4,9 ÷ 8 = 21,7 Bits por peso16 FP16, 8,5 Q8_0, ~4,9 Q4_K_MQ4_K_M es más o menos 0,6 bytes Memoria necesariapesos + caché KV + ~0,5 GB + 8 a 12 GB para el sistema Velocidad Techo del decodeancho de banda ÷ GB leídos por token128 ÷ 1,8 = 70 tok/s Decode realdel 30 al 50% del techo en este portátil11,7 medido; ~75% en ejecuciones densas publicadas PrefillTFLOPS útiles × 1e12 ÷ (2 × parámetros activos)0,6e12 ÷ 6e9 = 100 tok/s TTFTtokens del prompt ÷ tok/s de prefill6.500 ÷ 93 = 70 s Tiempo de respuestatokens de salida ÷ tok/s de decode300 ÷ 11,7 = 26 s Tiempo de pasoel mayor entre bytes ÷ ancho de banda y operaciones ÷ FLOP/snunca la suma; decode del 7B en una H100: 4,2 ms Punto de equilibrioFLOP/s ÷ ancho de banda, en tokens para fp16~295 en una H100, sea cual sea el tamaño del modelo Caché KV por token2 × capas KV × cabezas KV × tamaño de cabeza × bytes20 KB en Qwen3.6, 0,5 MB en el 7B Texto 1.000 palabras en inglés~1.300 tokensen español, de 1.300 a 1.600 Velocidad de lectura5 a 8 tok/spor encima parece instantáneo
La que más uso es el techo del decode. Un modelo que cabe pero escribe por debajo de la velocidad de lectura es un modelo que acabarás dejando de usar: en este portátil caben 60 GB de pesos densos, y escribirían unos dos tokens por segundo.
Respuestas cortas
Pregunta Respuesta corta
¿Necesito la atención y Q, K, V para ser ingeniero de IA aplicada? Como mecanismo, sí: de ellas salen la caché KV y todo lo que se construye encima. Las derivaciones, no.
¿Se guardan los resultados intermedios? Las activaciones se sobrescriben capa a capa; solo se guardan las claves y los valores de las capas de atención. Las consultas, nunca.
¿La caché KV es solo de la primera capa? Es por capa de atención, cada una a partir del residuo de esa capa; en Qwen3.6, las 10 capas de atención completa de 40.
¿Llega a estar el modelo entero en los núcleos de la GPU? Nunca: vive en la HBM (o en la RAM del portátil) y pasa a los núcleos en bloques de unos 32 KB, en cada paso.
¿Compré un mal ordenador? Compraste capacidad antes que velocidad: carga modelos que una tarjeta de 16 GB no puede, a velocidad de lectura.
¿La memoria intercambia expertos por token? No, los 256 siguen en memoria; aquí se ejecutan 8 por token, en Mixtral 2 de 8.
Recorrido de la petición
Volvamos a la pregunta del principio, con todo en su sitio.
1Ollama recibe los mensajes por HTTPlocalhost:114342La plantilla de chat los convierte en una cadena ChatML3El tokenizador la corta en unos 6.550 IDs de token4Cada ID se convierte en su fila de la tabla de embeddings2.048 números5El prefill pasa 13 trozos de 512 por las 40 capasunos 70 s, las 8 CU de la GPU ocupadas6Las diez capas de atención guardan claves y valores134 MB de caché KV7La última columna se puntúa contra 248.320 tokensla cabeza LM8El muestreador saca el primer token9Cada token siguiente es un paso de decodelee ~1,9 GB, ~85 ms10Tras unos 300 pasos el token de fin de turno para el bucle~26 s escribiendo
El minuto de silencio era la imagen de matrices enteras calculada una vez, en 8 unidades de cálculo con muchos tokens por peso. Los doce tokens por segundo eran una columna escuálida leyendo 1,84 GB por token de un bus de 128 GB/s. Casi todas las piezas de esta cadena existían antes de 2017, la atención incluida; lo que cambió el transformer fue el orden en que puede pasar el trabajo, todas las posiciones a la vez en el entrenamiento, y por eso escaló. La generación es el único sitio donde ese paralelismo se acaba, y cada truco de la segunda mitad del artículo consigue que esa única columna secuencial lea menos bytes por token o los comparta con otros.
11,7 tok/sQwen3.6 35B-A3B escribiendo en la Radeon 860M, frente a un techo de ancho de banda cerca de 70las medidas del artículo anterior 93 tok/sel mismo modelo leyendo un prompt de 2.900 tokensmisma ejecución 2,6 tok/sel denso Qwen3.8 27B escribiendo, que lee unos 16 GB por tokenmisma ejecución 4,2 msun paso de un 7B denso en fp16 en una H100, lo que tardan 14 GB a 3,35 TB/s, para 1 token o para 295la ficha técnica de NVIDIA, citada arriba 20 KBcaché KV por token de Qwen3.6 en f16, de 10 capas de atención de 40calculado de la cabecera GGUF 4,89 bitsbits por peso de media del archivo de 21,7 GBsumado de sus 753 tensores