Transformers and local inference
A course from zero on the transformer and on running one locally, 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 starts from one dot product and adds one idea at a time, until the two phases of inference and their bottlenecks follow from what came before. The second half sizes the KV cache with the real numbers of a hybrid model and works out when each trick for faster decoding stops paying. Eighteen small simulations, each with one control, sit between the steps, and a standalone simulator follows the whole request.
Créditos26 · oct 23Read notela mitad sobre el transformer empezó como apuntes del recorrido visual por los transformers de 3Blue1Brown, que sigue siendo el mejor sitio para coger intuición sobre la atención, y creció hasta el artículo anterior sobre transformers, que este absorbe y sustituye. El banner es un fotograma de ese mismo vídeo, animado con Manim, el motor de animación matemática de código abierto de Grant Sanderson. La mitad sobre ejecutarlo viene de una clase que me preparé sobre modelos locales y dejé a medias, y de las preguntas que no supe responder cuando intenté acabarla.
Tengo un portátil en la mesa y una pregunta para él. Es de las que recibiría un copiloto clínico, ¿alguna interacción con el anticoagulante nuevo?, y llega detrás de un prompt de sistema corto y de la historia de un paciente (sintética, unos 6.000 tokens, que viene a ser un documento de 4.000 palabras). El portátil ejecuta un modelo de lenguaje en local, así que los datos del paciente no salen de la máquina. Pulso enter y durante algo más de un minuto no pasa nada. Luego empieza la respuesta, y sale a unas doce palabras por segundo hasta que, unos 300 tokens después, se para.
De esos dos números, el minuto de silencio y los doce tokens por segundo, va este artículo. Quiero poder decir de dónde sale cada uno y cuánto costaría cambiarlo. Para llegar ahí voy a empezar por la pieza más pequeña posible (una multiplicación de dos listas de números) e ir añadiendo una idea cada vez, cada una apoyada en las anteriores. Si sabes qué es una lista de números, puedes seguirlo. Después de casi cada paso hay una figura interactiva pequeña con una sola cosa que tocar, y todas usan el mismo portátil y el mismo modelo, así que los números cuadran de una figura a otra.
El portátil es un Lenovo ThinkPad con un AMD Ryzen AI 7 PRO 450 y 64 GB de memoria; su único procesador gráfico es la Radeon 860M integrada en el mismo chip1. El modelo es Qwen3.6 35B-A3B, servido con Ollama. Las velocidades salen de un día que pasé midiendo este portátil, y los detalles del modelo de su propio archivo, que abrí byte a byte. Para quien tenga prisa, el simulador aparte sigue la petición entera desde el disco hasta el último token; todo lo que hay en él se explica abajo, pieza a pieza.
1AMD, página del Ryzen AI 7 PRO 450: 8 núcleos (4 Zen 5, 4 Zen 5c), 16 hilos, hasta 5,1 GHz, NPU de hasta 50 TOPS.
El producto escalar
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. Eso es un producto escalar. 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) sale 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.
Me gusta leerlo como una pregunta: ¿cuánto se parece xxx a www? Si dibujas las dos listas como flechas, el producto escalar es grande y positivo cuando apuntan igual, más o menos cero cuando son perpendiculares y negativo cuando apuntan en sentidos opuestos. Con dos números por flecha ya se ve, y lo mismo vale con tres o con 2.048.
x · w at 35°0.82 A dot product as two arrows: the closer their directions, the larger the number; perpendicular arrows give zero, opposite ones a negative number.
Las dos flechas miden uno, así que el producto escalar es el coseno del ángulo que forman; la barra cian es cuánto de www cae a lo largo de xxx. Una neurona, en el sentido de las redes neuronales, es exactamente esto: un producto escalar de sus entradas con sus pesos, normalmente seguido de una función que corta la parte negativa. Los pesos son la flecha www, y son lo que eligió el entrenamiento. Cuando se dice que un modelo tiene 35B parámetros, son 35.000 millones de estos números (la B es el billion inglés, mil millones).
Las capas como matrices
Una neurona hace una pregunta. Pon muchas en fila, todas leyendo la misma entrada, y tienes una matriz: cada fila son los pesos de una neurona, y multiplicar la matriz por un vector de entrada calcula a la vez el producto escalar de todas las filas. Una matriz de 4.096×4.0964.096 \times 4.0964.096×4.096 guarda 16,7 millones de pesos y convierte un vector de 4.096 números en otro de 4.096. Un modelo de lenguaje son unos cientos de matrices así, aplicadas una detrás de otra, y ejecutar el modelo es multiplicar vectores por ellas una y otra vez.
Aquí va el hecho en el que se apoya toda la segunda mitad del artículo, y quiero verlo en un juguete antes de que importe. Para multiplicar un vector por una matriz hay que traer de memoria cada peso una vez, y luego se usa para exactamente una multiplicación y una suma. Ahora multiplica dieciséis vectores por la misma matriz. Cada peso se sigue trayendo una vez, pero se usa dieciséis veces.
weights read48 multiply-adds for one token48 A 6 by 8 weight matrix read once and multiplied by one or more token columns: the reading stays at 48 weights, the multiply-adds grow with the tokens.
La lectura se queda en 48 pesos por muchas columnas que añadas; la aritmética crece con ellas. Guarda esta imagen, porque un modelo que escribe texto token a token es el extremo izquierdo de ese slider, con una sola columna. Que un ordenador esté ocupado multiplicando u ocupado esperando a que lleguen los números depende de cuántas columnas comparten cada lectura, y volveremos a ello con números reales cuando tengamos una máquina real.
Para contar, un peso cuesta una multiplicación y una suma, así que 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
En nuestro modelo son unos 6.000 millones de operaciones por token (toca unos 3.000 millones de pesos por token, por razones que llegarán con los expertos).
Tokens y embeddings
Una matriz quiere un vector, y nosotros tenemos texto. El modelo nunca ve letras: un tokenizador corta el texto en piezas de un vocabulario fijo y le pasa a la red un entero por pieza. Las palabras comunes son una pieza, las raras se parten en varias, y un espacio delante suele ir pegado a la palabra que sigue. El vocabulario de Qwen3.6 tiene 248.320 entradas (su archivo las lista todas; las seis primeras son !, ", #, $, % y &).
Las piezas se aprenden una vez, antes del entrenamiento, con BPE, y el algoritmo es tan sencillo que cabe en un navegador. Empiezas con caracteres sueltos (o bytes), buscas la pareja de vecinos que más se repite en un corpus, la pegas en una entrada nueva y repites hasta que el vocabulario tiene el tamaño que quieres. El archivo de Qwen guarda sus 247.587 fusiones en orden, y las primeras son dos espacios, cuatro espacios, i n y espacio t. La figura de abajo entrena uno sobre un solo párrafo clínico mientras miras; escribe lo que quieras y mira cómo lo corta.
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.
Aprende rápido las palabras de ese párrafo y parte todo lo demás en trocitos, porque un párrafo es todo lo que ha visto; el de Qwen se entrenó con terabytes. Puede parecer que sería más sencillo usar letras, y lo sería, pero sale caro. El texto en inglés y en español anda por unos cuatro caracteres por token, así que un modelo de caracteres manejaría cuatro veces más posiciones y (lo que más duele en un portátil) escribiría cuatro veces más. Cada posición escrita es una pasada entera por el modelo, así que una respuesta de 300 tokens se convertiría en unas 1.200 pasadas para las mismas palabras. Para presupuestar, 1.000 palabras en inglés son unos 1.300 tokens, y 1.000 en español entre 1.300 y 1.600.
Un chat es una lista de mensajes y el modelo solo continúa texto, así que una plantilla de chat aplana la lista en una sola cadena, con tokens especiales que marcan quién habla. La plantilla viaja dentro del archivo del modelo; Qwen usa el formato ChatML, y el modelo escribe a partir de la última línea:
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
Así que el prompt de sistema no tiene nada de misterioso: son las primeras líneas de un texto largo. Todo lo demás que lleva una petición, imágenes incluidas, acaba como tokens en esta misma cadena.
Cada ID de token se convierte después en un vector con una simple consulta. La matriz de embeddings tiene una fila por entrada del vocabulario, de 2.048 números en Qwen3.6, y el token 4.821 coge sin más la fila 4.821. Al empezar el entrenamiento las filas son ruido; el entrenamiento las va empujando hasta que tokens relacionados apuntan en direcciones relacionadas, que es todo lo que es un embedding. La tabla es grande pero casi gratis de usar, porque un token solo lee su propia fila.
Queda un problema. Consultadas una a una, perro muerde a hombre y hombre muerde a perro dan los mismos vectores, y nada de lo que tenemos sabe de orden. La codificación posicional lo arregla. Los modelos actuales, Qwen entre ellos, usan RoPE, que gira partes de cada vector un ángulo proporcional a su posición, de modo que cuando dos de ellos se comparan más tarde el resultado depende de lo lejos que están. El archivo de Qwen3.6 registra una base de rotación de diez millones, que es parte de cómo llega a un contexto de 262.144 tokens.
La atención
Tras la consulta tenemos una columna de 2.048 números por token, y esas columnas nunca se tiran. Cada bloque de la red las lee y les suma encima una corrección (una conexión residual), así que la columna de cada token es una suma acumulada en la que escribe cada capa, el flujo residual. El primer tipo de bloque deja que las columnas se pasen información.
Piensa en la palabra model. Tiene un solo embedding, y tiene que significar una cosa en a machine learning model y otra en a fashion model. La atención es el paso que deja que machine learning escriba en la columna de model, para que unas capas después la columna sepa cuál de los dos es.
El mecanismo son tres matrices más y el producto escalar que ya conocemos. Cada cabeza de atención multiplica la columna de cada token por tres matrices aprendidas, WQW_QWQ, WKW_KWK y WVW_VWV, y le da a cada token tres vectores cortos (consulta, clave y valor, de 256 números cada uno en Qwen3.6). Yo me lo imagino como un mercadillo, donde la consulta es lo que un token busca. La clave es lo que anuncia, y el valor es lo que entrega si lo eligen. La consulta de cada token se multiplica escalarmente por todas las claves (otra vez la pregunta: ¿cuánto se parecen?), las puntuaciones pasan por un softmax que las convierte en pesos entre 0 y 1 que suman 1, y lo que recibe el token es la suma de los valores ponderada por esos pesos.
Aquí está sobre una frase corta, con consultas y claves que escribí a mano para que el pronombre tenga a quién encontrar. Elige una palabra y mira adónde va su peso.
strongest pullwarfarin The attention weights of the pronoun its over the words before it: most of its weight goes to warfarin.
Desde its, casi todo el peso cae en warfarin, que es justo lo que querrías que absorbiera esa palabra. Una cabeza de verdad aprende sus vectores en vez de recibirlos escritos, y trabaja con 256 números por vector en vez de un puñado, pero la aritmética es la misma. En una fórmula, con dkd_kdk la longitud de una clave (dividir por su raíz cuadrada evita que los vectores largos den puntuaciones enormes que dejarían el softmax en 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
Habrás notado que en la figura ninguna palabra mira nunca a su derecha. Es a propósito, y resulta ser la regla con más consecuencias de todo el artículo. En un modelo que escribe texto, un token puede atender a sí mismo y a los anteriores, nunca a los posteriores: cada puntuación en la que una consulta se cruza con una clave posterior se pone a menos infinito antes del softmax, y e−∞=0e^{-\infty} = 0e−∞=0. Es la máscara causal. Existe para el entrenamiento (una frase de ocho palabras da ocho tareas de predicción en una sola pasada, y ninguna puede ver su propia respuesta), y tiene una consecuencia que se ve escribiendo la frase palabra a palabra.
rows computed per new word1 Attention weights of a sentence written word by word: future words are masked, and adding a word adds one row while the rows above stay exactly as they were.
Añade una palabra y aparece exactamente una fila nueva. Las de arriba no cambian ni un dígito, porque a ninguna de esas palabras se le dejó mirar a la nueva. La columna de un token en cada capa depende solo de él y de los tokens anteriores, así que añadir un token nunca cambia nada de lo que vino antes. Todo lo que calculemos sobre los tokens anteriores sigue siendo correcto para siempre y se puede guardar. Quédate con esto; es la razón de que exista la caché KV.
Una cabeza capta un tipo de relación, así que cada capa ejecuta varias en paralelo (atención multicabeza) y suma lo que aportan. 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. Ese reparto es la atención de consultas agrupadas (grouped-query attention), y va a importar para la memoria: guarda ocho veces menos claves y valores que si cada cabeza tuviera los suyos, a cambio de una pequeña pérdida de calidad1.
1Ainslie et al., GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints, 2023, que encontraron la forma agrupada cerca de la atención multicabeza completa en calidad y casi a la velocidad de un único juego compartido.
El MLP y sus expertos
Después de la atención, cada columna sigue sola por un MLP: dos (en los modelos actuales, tres) productos de matrices con una no linealidad en medio. La primera matriz se puede leer fila a fila como una lista de patrones, cada uno preguntando cuánto se le parece la columna; la no linealidad pone a cero los que puntuaron bajo; la segunda matriz suma a la columna una dirección por cada patrón que se disparó. Un patrón y la dirección que añade forman un pequeño dato guardado, y un MLP guarda decenas de miles por capa, que es donde parece vivir casi todo lo que sabe un modelo1. En un modelo normal (denso) los MLP guardan unos dos tercios de los parámetros, y todo token pasa por todos ellos.
1Geva et al., Transformer Feed-Forward Layers Are Key-Value Memories, 2021; Meng et al., Locating and Editing Factual Associations in GPT, 2022, que editaron datos sueltos cambiando pesos del MLP en capas intermedias.
Una mezcla de expertos (mixture of experts) parte ese MLP grande en muchos pequeños y añade un router que manda cada token a unos pocos. 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. Un router (otra matriz pequeña) puntúa los 256 para el token actual, se ejecutan los 8 mejores, y un experto compartido se ejecuta para todos los tokens. De ahí sale el nombre: 35.000 millones de parámetros en total, unos 3.000 millones activos por token, 35B-A3B.
experts per written token8 + 1 shared The 256 experts of one Qwen3.6 layer, lit by the tokens of one pass: 8 for a written token, many more for a 512-token prompt chunk.
Con un token en la pasada se despiertan 8 expertos por capa y el resto duerme. Pasa a un bloque de prompt de 512 tokens y se despiertan casi todos, porque 512 tokens eligen expertos distintos. Todos los expertos siguen en memoria todo el tiempo, porque el siguiente token puede elegir cualquiera, así que una mezcla de expertos no ahorra nada de capacidad; ahorra en lo que se lee y se calcula por token. Y ese ahorro es grande justo cuando hay un solo token en vuelo, que es cuando el modelo escribe. El reparto lo saqué con un generador aleatorio con semilla que favorece a unos expertos sobre otros, como hacen los routers de verdad, y por eso toca algunos menos que la fórmula de elecciones independientes.
Dos piezas más pequeñas completan la capa. Una normalización reescala cada columna antes de que la lea un bloque, para que los números ni exploten ni se apaguen a lo largo de 40 capas; casi no pesa. Y la sorpresa del archivo de Qwen3.6 es la clave full_attention_interval = 4: solo una capa de cada cuatro es la atención que acabamos de ver (diez de cuarenta). Las otras treinta son capas Gated DeltaNet, una forma de atención lineal que guarda un estado de tamaño fijo por capa, actualizado por cada token como en una red recurrente1. Un estado fijo no puede volver a un token concreto anterior como hace la atención, y por eso se quedan algunas capas de atención completa. Acuérdate del número diez; vuelve cuando contemos memoria.
1Yang, Kautz y Hatamizadeh, Gated Delta Networks: Improving Mamba2 with Delta Rule, ICLR 2025.
Muestreo del siguiente token
Tras la última capa, la columna del último token se multiplica por una matriz más, la cabeza LM, que tiene una fila por entrada del vocabulario. Son 248.320 productos escalares, una puntuación por cada posible siguiente token, los logits. Las puntuaciones pueden ser cualquier número real, así que un softmax las convierte en probabilidades: elevas eee a cada una (todo se vuelve positivo y las diferencias se estiran) y divides por el total. Antes, cada logit se divide 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
Esa división es toda la temperatura, y se entiende mejor tocándola que describiéndola. Aquí hay siete candidatos para la palabra que va después de lower the warfarin, con logits que me inventé.
top token at T = 1about 70% Seven candidate next tokens and their probabilities under a temperature slider: lower sharpens toward the favourite, higher flattens, the order never changes.
Los contornos discontinuos se quedan en temperatura 1 para comparar. Por debajo de 1 el favorito se lo queda todo; por encima de 1 la distribución se aplana y hasta banana tiene una oportunidad. El orden de los candidatos nunca cambia, y las probabilidades siempre suman uno. En el límite de cero el primero se lo lleva todo, que es la decodificación voraz (greedy).
El muestreo top-p hace otra cosa. Ordena los candidatos, se queda con el conjunto más pequeño desde arriba cuyas probabilidades suman al menos ppp, tira el resto y renormaliza los supervivientes. Holtzman y sus colegas lo propusieron al ver que muestrear de la distribución entera elige de vez en cuando un token de la larga cola de los casi imposibles, y que uno solo de esos puede descarrilar el resto del texto1.
1Holtzman et al., The Curious Case of Neural Text Degeneration, ICLR 2020.
kept at p = 0.93 of 7 The same candidates under a top-p cut: the smallest set from the top reaching p is kept and renormalised.
La temperatura remodela toda la distribución y deja viva la cola; top-p quita la cola y deja en paz las proporciones entre los que sobreviven. Las dos reducen la sorpresa, de formas distintas, y los modelos vienen con las dos puestas (el archivo de Qwen3.8 recomienda temperatura 1,0, top-p 0,95 y top-k 20, donde top-k conserva un número fijo de candidatos). El muestreador son unas pocas líneas en el motor, fuera de la red. Un atajo natural sería calcular solo los primeros logits, y no funciona: nadie sabe cuáles son los primeros hasta haber hecho los 248.320 productos escalares.
Un apunte sobre Claude: los modelos actuales de Anthropic ya no aceptan temperatura, top-p ni top-k. Desde Claude Opus 4.7, una petición que los fija devuelve un error, y desde la versión de la API del 21 de octubre de 2026 eso vale para todos los modelos1. La razón que dan es que el prompt es la forma de dirigir estos modelos. La mía, que es una suposición: un modelo ajustado con su muestreador fijo se porta peor justo cuando alguien cambia la distribución con la que se ajustó.
1Anthropic, guía de migración a la versión 2026-10-21 de la API, cambio 1, y las notas de migración de Opus 4.7, que recomiendan guiar el comportamiento con el prompt y recuerdan que la temperatura 0 nunca garantizó salidas idénticas.
Entrenamiento
Todo lo anterior convierte pesos en una predicción; el entrenamiento es el bucle que elige los pesos. Le das un lote de texto y predice el siguiente token en cada posición. Lo mal que lo ha hecho se mide con un solo número, la pérdida. La retropropagación calcula después para cada parámetro hacia dónde debería moverse para bajar ese número, y cada uno se mueve un pasito en esa dirección. La pérdida es la entropía cruzada: en cada posición coges la probabilidad que el modelo le dio al token que de verdad venía y sumas su logaritmo con signo cambiado, así que un fallo muy seguro es lo que más cuesta. La máscara causal es lo que lo hace rápido, porque cada posición de cada secuencia es un ejemplo de entrenamiento 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.
1Brown et al., Language Models are Few-Shot Learners, 2020, tabla 2.1.
El preentrenamiento da un modelo que continúa texto bien y sigue instrucciones mal. El ajuste fino es la familia de arreglos, de RLHF a LoRA, y el artículo sobre cómo aprenden los LLM la cuenta. Para ejecutar un modelo bastan tres datos. La inferencia nunca cambia los pesos, así que el modelo no aprende nada de tus preguntas y del paciente sabe solo lo que va en la petición. El ajuste fino necesita varias veces la memoria de la inferencia, y por eso se va a la nube. Y el tokenizador no se cambia nunca después del entrenamiento, porque cada fila de la tabla de embeddings pertenece a una de sus piezas.
El archivo GGUF
Hasta ahora un modelo han sido matrices en un papel. En mi disco es un archivo. Cuando Ollama descarga qwen3.6:35b-a3b guarda unos cuantos blobs en ~/.ollama/models/blobs, y el más grande, de 21.718.480.960 bytes, es el modelo: un archivo GGUF, el formato de llama.cpp. En un editor de texto se ve ruido, pero la estructura es tan sencilla que se lee con cuarenta líneas de Python, y hacerlo una vez es la forma más rápida que conozco de dejar de pensar en un modelo como algo misterioso.
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 son un diccionario, y todos los números que hemos usado hasta aquí están en él:
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.tokens = [248320 entries: '!', '"', '#', '$', ...] tokenizer.ggml.merges = [247587 entries: 'Ġ Ġ', 'ĠĠ ĠĠ', 'i n', 'Ġ t', ...] tokenizer.chat_template = {%- set image_count = namespace(value=0) %} ...
La tabla de tensores nombra cada matriz, y leer los nombres es leer la arquitectura. La capa 0 es una capa Gated DeltaNet, la 3 una de atención completa:
TEXT Copy
token_embd.weight [2048, 248320] Q4_K the embedding table blk.0.attn_qkv.weight [2048, 8192] Q6_K DeltaNet projections blk.0.ffn_gate_inp.weight [2048, 256] F32 the router blk.0.ffn_up_exps.weight [2048, 512, 256] Q4_K 256 experts in one tensor blk.0.ffn_up_shexp.weight [2048, 512] Q4_K the shared expert blk.3.attn_q.weight [2048, 8192] Q4_K full attention: Wq blk.3.attn_k.weight [2048, 512] Q4_K Wk, 2 heads x 256 blk.3.attn_v.weight [2048, 512] Q6_K Wv output.weight [2048, 248320] Q6_K the LM head
Sumando las formas salen 35.505.251.456 parámetros. Pero el archivo pesa 21,7 GB, y si cada parámetro ocupara los dos bytes de un número de 16 bits serían 71 GB. La diferencia está en la columna de formatos de la derecha, Q4_K y Q6_K.
Un peso sale del entrenamiento como un número en coma flotante de 16 bits. La cuantización lo guarda con menos bits, y lo primero que entendí mal es que no puede funcionar recortando decimales de cada número: 4 bits son dieciséis valores posibles y ya está. Lo que hace es coger un bloque de pesos (32, por ejemplo), buscar una escala para el bloque y guardar cada peso como el índice del más cercano de los niveles equiespaciados que permite esa escala. Recuperar un peso es una multiplicación, nivel por escala.
levels at 4 bits16 A block of 32 weights rounded to the levels one shared scale allows, at 8, 4, 3 or 2 bits, with the error of each weight.
Con 8 bits las barras guardadas caen encima de los puntos y el error no se ve. Con 4 bits hay dieciséis niveles y el error es de unos pocos por ciento, lo bastante pequeño para que casi todo el mundo ejecute los modelos así. Con 2 bits el bloque tiene cuatro niveles para repartir y el error se dispara. Fíjate también en el peso grande del bloque: obliga a una escala grande, y los pesos pequeños se quedan con pocos niveles cerca de cero entre los que elegir. Ese problema de los valores atípicos es lo que suavizan los formatos K, con un segundo nivel de escalas más pequeñas dentro de cada bloque.
Cada bloque guarda además su escala, y por eso los bits por peso nunca son números redondos: Q8_0 son 8 bits por peso más una escala de 16 bits cada 32 pesos, 8,5 bits. El archivo de Qwen3.6 mezcla Q4_K a 4,5 bits y Q6_K a 6,56 (las matrices a las que más les duele el redondeo se quedan con más bits) y sale a una media de 4,89 bits por peso. Eso es lo que significa Q4_K_M en la práctica, y la cuenta del tamaño de un modelo es siempre la misma: parámetros por bits por peso entre ocho, 35.500 millones por 4,89 entre 8, 21,7 GB.
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, porque el modelo tiene 27.000 millones de parámetros. Las dos cosas son ciertas: 27.300 millones de parámetros a 4,9 bits son 16,8 GB. Mis propios apuntes tenían el error contrario, un modelo de 20B con 17 GB en Q4; un modelo de 20.000 millones de parámetros en Q4_K_M ocupa unos 12 GB.
En el archivo no hay nada de código ejecutable. El motor pone la aritmética; el archivo pone los números y las formas. Por eso el mismo archivo se puede copiar o enlazar entre motores que leen GGUF, que es lo que hizo el artículo anterior entre Ollama y LM Studio.
Capacidad y ancho de banda de la memoria
Para calcular con un peso, un procesador lo necesita en un registro dentro del chip. El chip solo tiene unos pocos megabytes de memoria rápida propia, así que un modelo de 21,7 GB vive en la RAM, y cada peso que necesita un token tiene que cruzar el bus de la RAM al chip, en cada token. De esa memoria importan dos propiedades que es fácil confundir. La capacidad es cuánto cabe: aquí, 64 GB. El ancho de banda es lo rápido que entrega lo que guarda a quien calcula: como mucho 128 GB/s en este portátil.
La capacidad decide si un modelo cabe. El ancho de banda, resulta, decide a qué velocidad escribe. Aquí están los dos modelos que tengo en este portátil, la mezcla de expertos y uno denso de tamaño parecido, medidos de las dos maneras en el mismo eje que los 64 GB del portátil. Los botones cambian los bits por peso de los dos.
Qwen3.6 read per token1.84 GB Qwen3.8 27B read per token16 GB Memory held against bytes read per token for the mixture of experts and the dense model on the laptop.
El MoE ocupa más (21,7 GB) y lee muchísimo menos por token (unos 1,84 GB, sus 3.000 millones de pesos activos más las partes por las que pasa todo token). El denso de 27B ocupa menos y lo lee casi entero, unos 16 GB, en cada token. Guarda esa proporción, de más o menos nueve a uno.
El ancho de banda es una propiedad de la placa. La LPDDR5X-8000 hace 8.000 millones de transferencias por segundo, y el bus mide 128 bits, 16 bytes por transferencia: 8.000 millones por 16 bytes son 128 GB/s. Un bus más ancho significa más patas en el chip, más chips de memoria y más consumo, y por eso los portátiles finos se quedan en 128 bits; el Strix Halo de AMD usa 256 bits, y el M4 Max de Apple 512 bits para 546 GB/s1. Una tarjeta gráfica de sobremesa tiene su propia memoria, la VRAM, en un bus mucho más ancho: una RTX 5090 tiene 32 GB a 1.792 GB/s2. Un modelo que cabe en la VRAM va a velocidad de VRAM; uno que no cabe se reparte, una parte en la tarjeta y otra en la CPU desde la RAM del sistema, y marca el ritmo la parte lenta (ollama ps enseña ese reparto en su columna PROCESSOR).
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.
El portátil no tiene VRAM. Su GPU integrada, la iGPU, usa la RAM del sistema (memoria unificada), así que su memoria es grande y lenta. Es un intercambio, y no uno malo: una tarjeta de 16 GB no puede ni cargar este modelo de 21,7 GB, y el portátil lo carga sobrado. El chip tiene tres tipos de procesador alrededor de esa memoria. La CPU tiene 8 núcleos pensados para ejecutar rápido una sola secuencia de instrucciones. La iGPU tiene 8 unidades de cálculo de 64 carriles aritméticos sencillos cada una, 512 carriles haciendo la misma instrucción sobre datos distintos, que es justo lo que pide un producto escalar de 2.048 números. La NPU es un acelerador de matrices de bajo consumo que ni Ollama ni llama.cpp usan, así que está parada mientras el modelo trabaja.
iGPU del portátil RTX 5090 de sobremesa H100 en la nube
Memoria que lee la GPU 64 GB compartidos 32 GB de VRAM 80 GB HBM3
Ancho de banda 128 GB/s 1.792 GB/s 3.350 GB/s
Carga Qwen3.6 35B-A3B (21,7 GB) sí sí sí
Carga un modelo de 60 GB sí, despacio no, se reparte con la CPU sí
Unidades de cálculo 8 CU 170 SM 132 SM
Decode
Ya tenemos todas las piezas, así que sigamos a la respuesta mientras se escribe. El primer token de la respuesta ya existe y el modelo necesita el segundo. El token 2 depende del token 1, que hace un momento no existía, así que no hay atajo: una pasada entera por las 40 capas, con una sola columna de números. Esa pasada es un paso de decodificación, y en ella cada peso activo cruza el bus una vez para usarse en una multiplicación y una suma. Es el extremo izquierdo del slider de la segunda figura, con números de verdad.
Lo que nos deja la fórmula útil más sencilla del artículo. Si cada token tiene que leer 1,84 GB y la memoria entrega como mucho 128 GB por segundo, el modelo no puede escribir más de 128 entre 1,84, unos 70 tokens por segundo.
laptop ceilingabout 70 tok/s laptop measured11.7 tok/s One second of memory bandwidth cut into tokens of 1.84 GB each: the ceiling on writing speed for each machine.
Cada barra es un segundo de lectura en una máquina, las tres en el mismo eje, y en el portátil se ve cortada en bloques de los 1,84 GB de un token: caben unos 70. Los brillantes son lo que alcanza el motor de verdad, 11,7 tokens por segundo, medidos. Cambia al modelo denso y los bloques se hacen nueve veces más grandes. Las ejecuciones reales nunca tocan el techo (en este portátil la memoria entrega entre un 30 y un 40% de su ancho de banda teórico a este tipo de acceso, y cada una de las 40 capas añade un pequeño coste fijo por lanzar trabajo en la GPU), pero el techo es la primera estimación correcta, y explica la proporción entre modelos. El denso de 27B lee unos 16 GB por token, así que su techo son 8 tokens por segundo, y midió 2,6. Ocho veces más bytes, unas cuatro veces y media más lento. Ese es todo el argumento a favor de las mezclas de expertos en una máquina como esta.
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
Un tercio del ancho de banda no es lo que saca cualquier máquina. El modelo de tiempos de TokenWalk está ajustado sobre ejecuciones publicadas de llama-bench, y en las 67 que miden un modelo denso escribiendo, de un M1 a un 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 un 34% y el MoE un 17%, el MoE menos porque cada una de sus capas lee tan poco que el coste fijo por capa pesa más. Un Strix Halo, también una iGPU de AMD, llega al 77 a 81% con llama.cpp, así que ser integrada no explica la diferencia. Cuánto de ella es Ollama en Windows y cuánto este chip 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.
También explica por qué la cuantización importa aunque el modelo ya quepa. Menos bits por peso son menos bytes leídos por token, así que reducir los bytes a la mitad casi duplica la velocidad de escritura. La memoria vacía que sobra no vale nada mientras el modelo trabaja; los bytes leídos se pagan otra vez en cada token.
Mientras tanto, un paso de decode hace unas dos operaciones por peso, y un peso a 4,9 bits son unos 0,61 bytes, así que salen unas 3,3 operaciones por byte leído. La iGPU del portátil podría hacer unas 18 operaciones en lo que tarda en traer un byte. O sea, se pasa casi todo el paso de decode esperando. Esa espera es lo que significa estar limitado por memoria, y la proporción es la intensidad aritmética. El slider de abajo es el número de tokens que comparten cada lectura de los pesos.
writing, 1 tokenabout 3 per byte balanceabout 18 per byte Operations per byte read against the laptop’s balance point, as the tokens sharing each read go from 1 to 512.
Con un token el marcador está muy en el lado de la memoria. Hacia los ocho tokens por lectura cruza el punto de equilibrio, y a partir de ahí lo que limita es la aritmética. Escribir está atado a un token por lectura, por la dependencia entre tokens seguidos. Leer un prompt, no.
Prefill
Ahora, el minuto de silencio antes de la primera palabra. El prompt se conoce entero antes de generar nada, así que todos sus tokens pueden pasar juntos por las capas, igual que en el entrenamiento, con la máscara causal dejando a cada uno ciego a los que vienen detrás. Esto es el prefill. El motor mete el prompt en bloques de 512 tokens, así que cada peso que se lee de memoria sirve a 512 tokens, y el slider de antes dice qué pasa entonces: muy por encima del equilibrio, el chip está ocupado multiplicando y la lectura apenas importa.
Así que el prefill cuesta aritmética. Qwen3.6 necesita unos 6.000 millones de operaciones por token, 6.500 tokens de prompt son unos 39 billones de operaciones, y la GPU del portátil hace entre 0,6 y 0,75 billones de operaciones útiles por segundo en este trabajo. Divide y sale algo más de un minuto: el minuto de silencio antes de la primera palabra.
6,500 tokens on the laptopabout 70 s The wait before the first word as the prompt grows, on the laptop and on a desktop card.
La espera crece más o menos en proporción al prompt, y aquí una tarjeta de sobremesa cambia mucho más que en el decode, porque la diferencia en unidades de cálculo (8 contra 170) es mucho mayor que la de ancho de banda. Las dos fases hacen la misma aritmética por token y tienen cuellos de botella opuestos, y ese es el diagnóstico más útil que conozco para un modelo local.
Prefill
Decode
el prompt entero, en bloques de 512 Tokens por pasada uno
hasta 512 tokens Cada peso leído sirve a un token
la aritmética (unidades de cálculo) Lo limita el ancho de banda de memoria
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
Si una respuesta tarda en empezar, es el prefill, y las palancas están en el prompt. Menos pasajes y mejores lo acortan directamente, un resumen puede ocupar el lugar de la historia entera, y un principio fijo deja que el motor reutilice trabajo anterior (más sobre eso abajo). Si escribe despacio, es el decode, y la palanca son los bytes por token: un formato más pequeño o un modelo con menos parámetros activos lee menos, y una máquina con un bus más ancho los lee más deprisa. Las puntuaciones de la atención añaden un término que crece con el cuadrado de la longitud del prompt, porque cada token se compara con todos los anteriores, pero queda por debajo del resto de la pasada hasta que el contexto es varias veces el ancho del modelo1.
1Kaplan et al., Scaling Laws for Neural Language Models, 2020, tabla 1 y sección 2.1: el término de la atención queda por debajo del resto mientras el contexto es menor que unas doce veces el ancho del modelo.
La caché KV
Hay algo que me salté en el paso de decode. El token nuevo pasa por la atención, y la atención compara su consulta con las claves de todos los tokens anteriores, en cada capa de atención. Recalcular esas claves cada vez obligaría a pasar la red entera otra vez por los 6.500 tokens anteriores, en cada token nuevo: unos dos millones de pasadas de token para una respuesta de 300 tokens en lugar de 6.800.
La máscara causal nos da la salida. Las filas de los tokens anteriores nunca cambian, así que sus claves y valores, una vez calculados, valen para siempre. El motor los guarda, para cada token anterior y cada capa de atención, y la consulta del token nuevo se compara directamente con ellos. Ese almacén es la caché KV. Va por capa (un token nuevo en la capa 39 lee en qué se había convertido cada token anterior a la altura de la capa 39), y en Qwen3.6 solo las diez capas de atención completa guardan una; las treinta DeltaNet guardan su estado fijo. La lente de memoria de la consola la enseña llenándose una columna por token.
Su tamaño se cuenta en una línea. Cada capa de atención guarda una clave y un valor por token, cada uno con tantas cabezas KV por la longitud de cabeza, a tantos bytes por número.
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
Para Qwen3.6 con números de 16 bits sale 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. Aquí se cobran las dos decisiones que vimos antes: la atención de consultas agrupadas la dividió entre ocho, y guardarla solo en diez capas de cuarenta, entre cuatro.
per token20 KB at 16,384 tokens335 MB The KV cache of Qwen3.6 against the context reserved, next to the same model if every layer kept one.
La línea roja discontinua es más o menos lo que le queda libre al portátil con el modelo cargado, y la barra de abajo la cruza al final del slider. La memoria de la caché se reserva al cargar el modelo, para todo el contexto que pide la petición (num_ctx), lo use la conversación o no. Y aquí Ollama me sorprendió: calcula el valor por defecto a partir de la memoria que detecta, y en este portátil eligió 262.144 tokens, que son 5,4 GB de caché reservados para una conversación de 7.000 tokens. En un modelo sin el truco híbrido, la misma reserva sería cuatro veces mayor. Poner OLLAMA_CONTEXT_LENGTH (o num_ctx en cada petición) a lo que de verdad necesita la aplicación es el primer ajuste que yo cambiaría; la consola con la reserva de 262.144 tokens enseña lo que acapara.
Ya podemos hacer el presupuesto de memoria entero del portátil. Antes de cargar ningún modelo, Windows con Chrome, Cursor, Claude Code y Anki abiertos ya usaba 32 GB.
Qwen3.6 leaves freeabout 10 GB The laptop’s 64 GB filled by the system, a model’s weights, its KV cache and buffers.
Con Qwen3.6 cargado y 16.384 tokens de contexto quedan unos 10 GB libres, que es más o menos lo que yo querría dejar para que el sistema respire; el navegador crece hasta ocupar lo que haya, y un modelo que se va a disco es inservible. Con 64 GB, el problema de este portátil es la velocidad, nunca que quepa. La caché también se puede guardar con menos bits, aparte de los pesos (cuantización de la caché KV, OLLAMA_KV_CACHE_TYPE=q8_0 para más o menos la mitad de memoria). Conviene no mezclarlo: cuantizar los pesos redondea el modelo, igual para todas las peticiones; cuantizar la caché redondea la historia del paciente tal como el modelo la ha leído.
Aquí vive un truco más. La rejilla de puntuaciones de atención sobre NNN tokens tiene N2N^2N2 casillas, y escrita sin más se queda en la memoria principal entre el paso que la calcula y el softmax que la normaliza. FlashAttention nunca la escribe entera: la recorre en bloques que caben en la memoria propia del chip, con un softmax que se va acumulando1. El resultado son exactamente los mismos números calculados en otro orden, con una memoria de atención que crece de forma lineal y no cuadrática. Ollama la exige (OLLAMA_FLASH_ATTENTION=1) para cuantizar la caché.
1Dao et al., FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness, 2022.
Speculative decoding
El decode desperdicia el chip: un token por lectura, las unidades aritméticas casi siempre paradas. El prefill enseña lo que hace el mismo chip con muchos tokens por lectura. El speculative decoding toma eso prestado. Un modelo borrador pequeño que comparte el tokenizador del grande (Qwen3 0.6B para un Qwen, por ejemplo) escribe unos cuantos tokens por delante, barato. El modelo grande los revisa todos en una pasada, que es un prefill diminuto y cuesta más o menos lo mismo que un paso de decode normal, porque los pesos se leen una vez para todos. Se queda con los tokens del borrador desde la izquierda mientras coinciden con lo que él habría producido, y en el primer desacuerdo pone su propio token y tira el resto.
La salida no cambia. Con muestreo, una regla de rechazo hace que los tokens aceptados sigan exactamente la distribución del modelo grande1. Solo cambia la velocidad, y depende de cuántas veces acierta el borrador, la tasa de aceptación α\alphaα.
1Leviathan, Kalman y Matias, Fast Inference from Transformers via Speculative Decoding, ICML 2023, que demuestran que la distribución de salida se conserva y derivan la aceleración esperada.
tokens per round at α = 0.72.8 A round of speculative decoding with four draft tokens: the chance of reaching each slot before the first rejection.
Con cuatro tokens de borrador, el primer token de una ronda sale siempre (lo escribe el modelo grande si el borrador falló), el segundo necesita una aceptación, el tercero dos seguidas, y así; si se aceptan los cuatro, la misma pasada da un quinto gratis. Con α=0,7\alpha = 0{,}7α=0,7, una ronda da 2,8 tokens de media por una sola pasada del modelo grande.
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
Que compense o no depende del modelo, y en nuestros dos modelos sale al revés, que es la parte que más me interesó.
dense 27Bfaster MoEslower The time of one round with and without a draft model, for the dense 27B and for the mixture of experts.
En el denso de 27B la revisión cuesta más o menos un paso normal (lee 16 GB una vez, revise un token o cinco), y cuatro pasos de un borrador de 0.6B son baratos a su lado, así que una ronda va más o menos el doble de rápido. En el MoE el mismo truco pierde. Su paso normal es barato, porque lee 1,84 GB, y una revisión de cinco tokens no lo es: cinco tokens despiertan hasta cuarenta expertos por capa en vez de ocho (la figura de los expertos, con cinco tokens en la pasada), así que la revisión lee varias veces más, y los pasos del borrador encima cuestan más que los tokens que ahorran. En mis apuntes de la clase esto era una corazonada; la figura lo calcula con costes de primer orden, y medirlo es el experimento pendiente. La consola ejecuta los dos, el denso de 27B con borrador y el MoE con el mismo borrador. El archivo de Qwen3.6 lleva incluso un borrador de serie: su bloque 41 es una capa de predicción de varios tokens entrenada para adivinar un token más allá. Ollama sí la usa, por defecto con dos borradores por ronda, y en este portátil solo compensa cuando el muestreo es casi voraz: a temperatura 0 subió la escritura de 13,2 a 15,6 tokens por segundo, a 0,7 en cambio costó tiempo (11,5 y 10,6 tokens por segundo en dos ejecuciones, frente a 13,2 sin ella), porque una apuesta más barata se rechaza más a menudo.
Prefix caching y ciclo de vida del modelo
Un modelo no recuerda nada entre peticiones. Los pesos no cambian, y la caché KV es de la petición que la construyó. Un chat parece continuo porque la aplicación vuelve a mandar la conversación entera cada vez, desde el prompt de sistema hasta la pregunta nueva del final. Todo ese texto es la ventana de contexto de esa llamada, y cada llamada paga prefill sobre todo él, salvo que el motor se dé cuenta de que ya ha visto el principio.
Eso es el prefix caching. La llamada 4 de una conversación empieza con los mismos tokens que la 3, y por la máscara causal sus claves y valores también son idénticos, así que un motor que los guardó calcula solo la cola nueva. La coincidencia tiene que ser exacta desde el primer token, y esa es la parte que muerde en la práctica.
call 4, prompt appendedabout 350 tokens computed Four calls of one conversation: the tokens the engine reuses from the previous call and the ones it has to compute again.
Añade la pregunta nueva y la llamada 4 calcula unos cientos de tokens. Cambia una palabra del prompt de sistema y todo lo que va detrás de esa palabra tiene claves y valores distintos, porque cada token posterior la atendió, así que la llamada 4 recalcula los 7.600 tokens y vuelve a pagar el minuto entero. De ahí salen dos costumbres: dejar que el contexto crezca añadiendo, con lo estable delante y la pregunta variable al final; y no juzgar el coste contando tokens, porque un prefijo de 20.000 tokens en caché puede salir más barato que uno nuevo de 4.000. Las API cobran justo esto; 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 leerlo después1. Ollama guarda la caché de la última petición en el modelo cargado, cosa que descubrí por casualidad cuando una prueba reutilizó el mismo principio de prompt y leyó 2.900 tokens a entre 7.000 y 9.000 tokens por segundo.
1Anthropic, Prompt caching, precios y el orden de invalidación herramientas, sistema, mensajes.
La caché vive en memoria y muere con el modelo. El servidor de llama.cpp también puede guardar la caché de un slot en disco (--slot-save-path) y cargarla después. Para el copiloto clínico eso cambia las cuentas: la historia de 6.500 tokens cuesta unos 70 segundos de prefill en este portátil y unos 133 MB de caché, que un SSD lee en una décima de segundo. Calcula una vez la caché de cada paciente, restáurala al abrir el paciente, y el primer token llega en más o menos un segundo. En un modelo híbrido el estado guardado tiene que incluir también el estado fijo de las capas DeltaNet, y todavía no he probado a guardar slots con esta arquitectura.
El propio modelo también puede salir de la memoria. Ollama es un servidor en segundo plano (escucha en localhost:11434, y todo lo demás, el comando ollama incluido, es cliente suyo), 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 ajuste que decide el último paso, keep-alive, son cinco minutos por defecto, e importa más de lo que parece en una herramienta interactiva, porque la primera petición después de descargar paga la carga entera otra vez.
load on the laptop55 s Three questions on a timeline: with gaps shorter than keep_alive the model stays loaded; longer gaps make every question load it again.
Haz una pregunta cada tres minutos y el modelo siempre está ahí. Haz una cada diez y cada pregunta espera 55 segundos de carga antes de que empiece siquiera el prefill, como enseña 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
Las variables de entorno las lee el servidor al arrancar, así que necesitan reiniciarlo: en Windows, setx OLLAMA_CONTEXT_LENGTH 16384 y las demás, luego cerrar Ollama desde la bandeja y volver a abrirlo; en Linux, sudo systemctl edit ollama con una línea Environment= por variable. Ponerlas con export en una terminal no le hace nada a un servidor que ya está en marcha. Las opciones que van en una petición (num_ctx, keep_alive, la temperatura) las sustituyen solo para esa llamada, y cada petición 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 no es la única forma de ejecutar un archivo GGUF, y conviene conocer las otras dos.
Ollama LM Studio servidor de llama.cpp
Qué es un servicio con CLI y API una aplicación de escritorio con servidor el propio motor, compilado por ti
Speculative decoding con borrador no expuesto sí --model-draft
Guardar la caché KV en disco no no --slot-save-path
Para qué scripts, servicio siempre encendido probar modelos, una interfaz control total, investigación
Ollama esconde las opciones del motor y te gestiona archivos y memoria; llama.cpp lo expone todo y no gestiona nada. Un proyecto que necesita speculative decoding o cachés guardadas en disco se pasa al servidor de llama.cpp, y todo lo demás es más fácil en Ollama.
La recuperación y los límites del portátil
Casi todos los prompts largos de una aplicación real salen de la generación aumentada por recuperación (RAG): buscar en tus documentos los pasajes relevantes para la pregunta y pegarlos en el prompt, para que el modelo responda a partir de ellos y los cite. La búsqueda suele combinar BM25, que puntúa los pasajes por las palabras exactas que comparten con la pregunta (excelente con nombres de fármacos como warfarin y amiodarone, ciego a los sinónimos), con la búsqueda por embeddings, que compara vectores de un modelo de embeddings y encuentra anticoagulante oral para warfarina (aunque puede perderse un código exacto). Ejecutar las dos y fusionar las clasificaciones es la búsqueda híbrida. En los términos de este artículo, la recuperación decide cuánto texto entra en el prefill. A unos 90 tokens por segundo en el portátil, diez pasajes de 500 tokens añaden casi un minuto antes de la primera palabra, así que recuperar menos pasajes y mejores es la aceleración más barata que tiene un sistema RAG.
El portátil ejecuta un modelo de 35B en privado y gratis por petición, más o menos a velocidad de lectura. Es la máquina equivocada para el trabajo que necesita mucha aritmética durante mucho tiempo. Una evaluación sobre miles de casos de prueba carga sobre todo prefill, y lo mismo un trabajo por lotes sobre muchos documentos; el ajuste fino es entrenamiento. Ahí es justo donde más duelen 8 unidades de cálculo. Una GPU alquilada los hace en una fracción del tiempo (una RTX 5090 costaba 0,69 dólares la hora en la nube comunitaria de RunPod en octubre de 20261, y es lo que usé para el ajuste fino de Stable Diffusion). El portátil se queda con lo que tiene que ser privado, y con un asistente que siempre está ahí.
1RunPod, Pricing, página actualizada el 8 de octubre de 2026.
Cuentas de servilleta
Casi todas las preguntas sobre si un modelo va a funcionar, y a qué velocidad, se pueden responder antes de descargar nada, con las conversiones que hemos ido construyendo.
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 Caché KV por token2 × capas KV × cabezas KV × tamaño de cabeza × bytes20 KB en Qwen3.6 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
Escribí este artículo para responder una lista de preguntas que tenía tras dejar la clase a medias. Aquí están, cada una con su respuesta corta.
Pregunta Respuesta corta
¿Necesito la atención y Q, K, V para ser ingeniero de IA aplicada? Como mecanismo, sí, porque de ellas salen la caché KV y todo lo que se construye encima. Las derivaciones, no.
¿Cada capa es una matriz por un vector? Una capa son unas pocas matrices; en decode cada una se multiplica por un vector, en prefill por una matriz de hasta 512 columnas.
¿El decode solo calcula las multiplicaciones nuevas y unas sumas? Hace la pasada entera del token nuevo por todas las capas, leyendo cada peso activo; se salta todo lo de los tokens anteriores.
¿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, en la caché KV.
¿La caché KV es solo de la primera capa? Es por capa de atención, todas; en Qwen3.6, las 10 capas de atención completa de 40.
¿Un GGUF es una matriz gigante de pesos? Un archivo de cientos de tensores con nombre detrás de una cabecera cuyos metadatos llevan el tokenizador y la plantilla de chat.
¿Cuantizar es cortar decimales? Redondear cada peso a uno de unos pocos niveles que fija una escala compartida por bloque; 4 bits son 16 niveles.
¿Por qué las diapositivas ponen ~1 byte y ~½ byte? Cada bloque guarda además su escala, así que Q8_0 son 8,5 bits y Q4_K_M unos 4,9.
¿Eran 27 GB o 17 GB? 27.300 millones de parámetros, 16,8 GB en Q4_K_M. Cuenta y tamaño son números distintos.
Si el modelo cabe, ¿sigue ayudando que ocupe menos? Sí, cada token lee cada peso activo, así que los bytes por peso se pagan en cada token.
¿Por qué tokens y no letras? Cuatro veces más posiciones: cuatro veces más pasadas de decode.
¿Cuántos tokens tiene una redacción de 300 palabras? Entre 400 y 500.
¿Podría la última capa puntuar solo los 10 mejores tokens? Nadie sabe cuáles son los mejores antes de puntuar los 248.320, así que los motores los calculan todos.
¿Qué es la temperatura, por dentro? Cada logit se divide por T antes del softmax.
¿Las probabilidades no suman siempre 1? ¿El muestreo las reordena? Siempre suman 1; top-p se queda con el menor conjunto de cabeza que llega a p y renormaliza; nada se reordena.
¿Por qué no puedo poner la temperatura de Claude? La API la rechaza desde Opus 4.7, y en todos los modelos desde la versión 2026-10-21; el prompt la sustituye.
¿Por qué 16 tok/s con el MoE? Una lectura temprana y sucia; la limpia es 11,7, frente a un techo cerca de 70 que fijan 1,8 GB por token.
¿Una tarjeta NVIDIA dedicada es mejor por CUDA? Por su propia memoria rápida (VRAM) y sus muchas unidades de cálculo; CUDA es el software que las usa.
¿Qué son las CU? ¿La NPU es un procesador paralelo? Las CU son los bloques de cálculo de la iGPU (8 aquí); la NPU es un acelerador de matrices de bajo consumo que Ollama no usa.
¿Mi portátil tiene GPU? Sí, la 860M integrada, que también pinta la pantalla.
¿El decode lo limita la CPU? Lo limita el ancho de banda de memoria, sea cual sea el procesador que lo ejecuta.
¿Cómo va a ser un cuello de botella 128 GB/s con 64 GB? Lee la memoria entera unas dos veces por segundo; un token tiene que leer de 1,8 a 16 GB de ella.
¿Qué es un bus de 128 bits y por qué no más ancho? 16 bytes por transferencia, 8.000 millones de transferencias por segundo; uno más ancho necesita más patas en el chip y más consumo.
¿La VRAM es peor porque es más pequeña? Más pequeña y de 7 a 25 veces más rápida; un modelo que no cabe se reparte con la CPU y se frena.
¿Compré un mal ordenador? Compraste capacidad antes que velocidad: carga modelos que una tarjeta de 16 GB no puede, a velocidad de lectura.
¿Siempre son 8 expertos? ¿La memoria intercambia por token? 8 de 256 aquí, 2 de 8 en Mixtral; nada se intercambia, todos los expertos siguen en memoria.
¿Cada pregunta reenvía todo? Sí, cada vez; el prefix caching evita recalcular el principio repetido.
¿Cuánta memoria debería quedar libre? De 8 a 12 GB además del modelo; Windows y las aplicaciones de aquí ocupaban 32 GB antes de cargarlo.
¿Qué tiene que ver fetch, decode, execute con pull, load, serve? Uno es el ciclo de instrucciones de una CPU y el otro la vida de un modelo en Ollama; nada, aunque compartan palabra.
¿Para qué un borrador pequeño si el grande hace lo mismo? Adivina barato; el grande revisa varias suposiciones en una sola lectura de sus pesos.
¿Qué significa cuantizar la caché KV? Redondear el contexto guardado en vez de los pesos: la mitad de memoria de caché con q8_0.
Recorrido de la petición
Volvamos a la pregunta del principio, en el portátil, con todo en su sitio.
1Ollama recibe los mensajes por HTTPlocalhost:11434
1La plantilla de chat los convierte en una cadena ChatML
1El tokenizador la corta en unos 6.550 IDs de token
1Cada ID se convierte en su fila de la tabla de embeddings2.048 números
1El prefill pasa 13 bloques de 512 por las 40 capasunos 70 s, las 8 CU de la GPU ocupadas
1Las diez capas de atención guardan claves y valores134 MB de caché KV
1La última columna se puntúa contra 248.320 tokensla cabeza LM
1El muestreador saca el primer token
1Cada token siguiente es un paso de decodelee ~1,9 GB, ~85 ms
1Tras unos 300 pasos el token de fin de turno para el bucle~26 s escribiendo
El minuto de silencio era el prefill: 6.500 tokens por 6.000 millones de operaciones cada uno, en 8 unidades de cálculo. Los doce tokens por segundo eran el decode: 1,84 GB leídos por token en un bus de 128 GB/s, a más o menos un tercio de su velocidad teórica, con Ollama adelantando dos tokens por paso con la cabeza de predicción del propio modelo, que a esta temperatura cuesta más o menos lo que ahorra (13,2 tokens por segundo sin adelantar, 15,6 adelantando a temperatura 0). 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: el entrenamiento lee todas las posiciones a la vez, y por eso escaló. La generación es el único sitio donde ese paralelismo se acaba, un token detrás de otro a través de la memoria, y cada truco de la segunda mitad del artículo es una forma de que ese único paso secuencial lea menos bytes.
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 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 2,8tokens por ronda de speculative decoding con α = 0,7 y cuatro tokens de borradorLeviathan et al., 2023