SenseiTeoría desde cero0/21

3 · La computadora por dentroLección 2 de 5

Memoria, caché y disco

Memory, cache and disk

Dónde viven los bytes de un modelo y de una foto, por qué el mismo dato tarda 1 nanosegundo o 10 milisegundos según el piso en que esté, y qué pasa cuando la memoria se acaba.

⏱ 60 min lo esencial · 78 min todo11 conceptos nuevos3 calculadoras10 ejerciciosModelo y API
Secciones14
  1. 01Dos memorias: la RAM y el disco
  2. 02La caché de la CPU y la escalera de memoria
  3. 03Discos por dentro: disco duro, SSD y discos de la nube extra
  4. 04Localidad: por qué recorrer en orden es más rápido
  5. 05Memoria virtual: cada proceso ve su propio mapa
  6. 06Cargar el modelo: la page cache y mmap
  7. 07Cuando la RAM se acaba: swap y OOM
  8. 08La memoria de un contenedor: workers y memoria compartida extra
  9. 09Errores comunes
  10. 10En la vida real extra
  11. 11Ejercicios
  12. 12Lo que sigue
  13. 13Palabras que vas a oír
  14. 14Chequeo
La pregunta

¿Dónde viven los bytes de un modelo y de una foto, y por qué el mismo dato tarda 1 ns o 10 ms según dónde esté?

En la lección de FLOPs viste que una GPU puede pasar más tiempo esperando bytes que calculando. Aquí abrimos la computadora completa para ver dónde esperan esos bytes.

Piensa en el servidor de GPU de C01. El modelo es un archivo de ≈ 50 a 150 MB guardado en un disco, y para responder tiene que estar en la memoria de la GPU. La foto de Ana hace otro viaje: nunca toca un disco. Llega por la red, vive unos milisegundos en la memoria y desaparece.

Al terminar vas a poder contestar preguntas de todos los días en producción. ¿Por qué recorrer una tabla en orden es varias veces más rápido? ¿Por qué un LLM tarda minutos en arrancar la primera vez y segundos la segunda? ¿Por qué a veces un servidor al que le falta memoria no se cae, sino que se vuelve lentísimo? ¿Y por qué un contenedor muere con «OOMKilled» aunque a la máquina le sobre memoria?

01

Dos memorias: la RAM y el disco

Esencial

Two memories: RAM and disk

Tu celular lo dice en la caja con dos números: «128 GB» y «8 GB de RAM». El primero es el almacenamiento, el disco; el segundo, la memoria. No son lo mismo.

La RAM: la memoria de trabajo

La RAM es donde viven los programas mientras corren y los datos que están usando. Imagínala como una fila enorme de casillas de un byte, cada una con su número, su dirección: la casilla 0, la 1, la 2… hasta miles de millones. Para leer, la CPU da la dirección y la RAM entrega el byte.

Se llama RAM, random access memory («memoria de acceso aleatorio»), porque cualquier casilla tarda más o menos lo mismo, sin importar cuál pediste antes: ≈ 100 ns, como viste en la lección de unidades. Y una vez que empieza a entregar, da muchos bytes seguidos: de decenas a cientos de GB por segundo, según la máquina. La Mac M4 donde se escribió esta lección da 120 GB/s; un servidor grande, varios cientos. Son dos números distintos, y toda memoria tiene los dos: cuánto tarda en llegar lo primero que pides (su latencia) y cuántos bytes por segundo entrega después (su ancho de banda).

Tiene dos debilidades. La primera: se borra al apagar. Cada bit es una carguita eléctrica en un capacitor diminuto que se escapa sola; el chip la refresca cada pocas decenas de milisegundos, y sin luz se pierde todo. Por eso se le dice DRAM, RAM dinámica. La segunda: es cara. Por eso una laptop trae de 8 a 32 GB, y un servidor, de decenas de GB a varios TB.

El disco: el almacén que no olvida

El disco guarda los archivos aunque se apague la computadora: el sistema, los programas instalados, tus fotos, un dataset, los pesos de un modelo. Cabe mucho más y cuesta mucho menos por GB que la RAM, pero tarda en encontrar cada dato. Por dentro también es una fila de casillas numeradas, solo que más grandes: bloques de 512 bytes o de 4 KiB, según el disco. Se lee de bloque en bloque, nunca un byte suelto, y el sistema operativo casi siempre pide 4 KiB o más a la vez. Hay dos tipos:

  • El disco duro (HDD, hard disk drive): platos que giran y un brazo que lee. Encontrar un dato tarda ≈ 10 ms, porque hay que mover piezas.
  • El SSD (solid-state drive): chips de memoria flash, la misma de una memoria USB, sin nada que se mueva. Encuentra un dato en ≈ 0.05 a 0.1 ms. Se conecta por SATA, la conexión que se diseñó para los discos duros (hasta ≈ 550 MB/s), o por NVMe, una hecha para flash que da varios GB/s.
RAMSSD NVMeDisco duro
¿Guarda sin luz?NoSíSí
Tamaño típico8 GB a varios TBcientos de GB a decenas de TBhasta ≈ 40 TB
Encontrar un dato (latencia)≈ 100 ns (0.0001 ms)≈ 0.05 a 0.1 ms≈ 10 ms
Leer de corrido (ancho de banda)decenas a cientos de GB/s1 a 14 GB/s0.1 a 0.3 GB/s
Por GB, cuestalo más carounas cien veces menos (2026)unas veces menos que un SSD

¿Por qué no usar solo una? Porque no existe una memoria rápida, grande, barata y que no olvide. La RAM es rápida, pero olvida y es cara; el disco recuerda y es barato, pero es demasiado lento para calcular con él. Así que cada máquina tiene las dos, y una regla manda: la CPU solo calcula con lo que está en la RAM. Todo programa y todo archivo se carga, se copia del disco a la RAM, antes de usarse. Es lo que pasa en los segundos que tarda en abrir una app.

La GPU tiene además su propia memoria, la VRAM, aparte de la RAM. Para que calcule, los bytes tienen que cruzar a ella por PCIe, el camino que une la CPU con la GPU (≈ 16 GB/s en la T4 de C01).

Dónde viven los bytes de C01

Junta las piezas en el servidor de GPU de C01, una máquina que en Kubernetes se llama nodo. El modelo es un archivo, el engine de TensorRT (la ResNet-50 ya convertida para correr rápido en una GPU de NVIDIA). Vive en el disco, dentro de la imagen de Docker de Triton, el programa que sirve el modelo (la imagen es el paquete con el que se arma su contenedor). Al arrancar, Triton lo lee a la RAM y copia los pesos a la VRAM: ≈ 49 MB (24.5 millones de parámetros de 2 bytes, en FP16), que se quedan ahí mientras el servidor viva.

La foto de Ana hace otro viaje, y nunca toca un disco. El gateway ya la decodificó y la preparó: llega por la red como un tensor de 602 KB, pasa por la RAM y por la VRAM y, unos milisegundos después, ya no existe.

Dónde vive cada byte en el servidor de GPU de C01 · Toca cada caja para ver qué guarda. Usa «Recorrer» para seguir el arranque, la foto de Ana y lo que pasa si se va la luz.
  • Disco del nodo · la imagen y el engine
    El disco guarda los archivos aunque la máquina se apague: la imagen de Docker de Triton y, dentro, el engine de la ResNet-50 (≈ 50 a 150 MB). Leer de aquí tarda de ≈ 0.1 ms a unos milisegundos por lectura.
  • Red · llega el tensor de Ana
    El gateway ya decodificó y preparó la foto de Ana. Por la red llega un tensor float32 de 602 KB, que se escribe directo en la RAM: la foto nunca toca un disco.
  • RAM del nodo · 16 GiB · ≈ 100 ns
    La RAM, la memoria de trabajo. Aquí viven Triton mientras corre, el tensor de cada request y la copia del engine que se leyó al arrancar. Se borra si se va la luz.
  • CPU · núcleos y su caché
    Los núcleos de la CPU solo calculan con lo que les llega a su caché, que responde en unos pocos nanosegundos. Aquí corre el código de Triton: recibe la request, acomoda el tensor y le encarga el trabajo a la GPU.
  • VRAM · 16 GB · pesos en FP16
    La VRAM, la memoria propia de la GPU. Los ≈ 49 MB de pesos en FP16 se copian aquí una vez, al arrancar, y se quedan mientras el servidor viva.
  • Núcleos de la GPU · la ResNet-50: 3–6 ms
    Los núcleos de la GPU calculan con lo que está en la VRAM, pasando por sus propias cachés. La ResNet-50 tarda ≈ 3 a 6 ms por foto.
02

La caché de la CPU y la escalera de memoria

Esencial

The CPU cache and the memory ladder

El problema: la CPU espera

Un núcleo de la CPU, cada uno de los procesadores que trae el chip, hace varias operaciones por nanosegundo. Si cada número viniera de la RAM, en los 100 ns de espera podría haber hecho cientos de operaciones: se pasaría casi todo el tiempo esperando. Es el muro de la memoria (memory wall): desde hace décadas, las CPU se aceleraron mucho más que la RAM.

La solución: memorias pequeñitas junto al núcleo

Dentro del chip de la CPU hay memorias diminutas y muy rápidas que guardan copia de lo que se usó hace poco: la caché (cache). Están hechas con SRAM, RAM estática: cada bit es un circuito de seis transistores que no necesita refrescarse. Es rápida, pero ocupa mucho espacio del chip y es cara. Por eso hay poca, en varios niveles:

NivelTamaño típicoTardaDe quién es
L132 a 128 KiB≈ 1 nsde cada núcleo
L21 a 16 MiB≈ 3 a 5 nsde cada núcleo o de un grupo
L3decenas a cientos de MiB≈ 10 a 30 nsde un grupo o de todos

Son valores de CPUs de 2022 a 2025. En Linux, lscpu te dice las de tu máquina.

Para ir más hondoLas cachés de dos máquinas de verdad

En la Mac con chip M4 donde se escribió esta lección, cada núcleo rápido tiene 128 KiB de L1 para datos, y los 4 núcleos rápidos comparten 16 MiB de L2; Apple no tiene una L3 como tal, sino otra caché que comparte con la GPU. Un Ryzen 9 9950X (AMD, 2024) trae por núcleo 48 KiB de L1 para datos y 1 MiB de L2, y 32 MiB de L3 por cada 8 núcleos.

Aciertos, fallos y líneas

Cuando el núcleo necesita un dato, lo busca primero en la L1. Si está, es un acierto (cache hit): llega en ≈ 1 ns. Si no, es un fallo (cache miss): lo busca en la L2, luego en la L3 y al final en la RAM. De regreso, el dato se queda copiado en las cachés, y para hacerle lugar sale lo que lleva más tiempo sin usarse.

La caché nunca trae un byte suelto. Trae un bloque de 64 bytes seguidos, una línea de caché (cache line); en los chips de Apple, 128. Un float32 ocupa 4 bytes, así que una línea trae 16: pides uno y llegan sus 15 vecinos. Nadie lo pide: el hardware hace todo esto solo, sin que el programa se entere.

¿Cuánto pesan los fallos? Supón, para el ejemplo, que 95 de cada 100 lecturas aciertan en la L1 y que las otras 5 van hasta la RAM (para simplificar, sin contar la L2 ni la L3):

Cuánto tarda una lectura, en promedioLa cuenta
Las 95 de cada 100 que aciertanen la L1
95 × 1 ns
95 ns
Las 5 que fallanvan a la RAM
5 × 100 ns
500 ns
Las 100 juntas
95 + 500
595 ns
Promedio
595 ÷ 100
≈ 6 ns
Resultado
Con 95 % de aciertos, una lectura tarda en promedio 6 veces lo de un acierto, y el 84 % del tiempo se va en ese 5 % de fallos. Con 99 %, el promedio baja a ≈ 2 ns.
Aciertos y fallos: el tiempo promedio de una lecturaCalculadora

De cada 100 lecturas, unas encuentran el dato en la memoria rápida (un acierto) y otras tienen que ir a la lenta (un fallo). Arranca con el ejemplo del texto: 95 % de aciertos en la caché a 1 ns y fallos que van a la RAM, a 100 ns.

Tiempo promedio por lectura, en ns5.95
Veces más lento que si todo acertara6.0×
Parte del tiempo que se va en los fallosaunque sean pocos, mandan84.0 %
Un millón de lecturas tardanun millón de ns es 1 ms5.95 ms

Pon 99 %: el promedio baja a ≈ 2 ns. Vas a volver aquí dos veces más: en «Localidad», con 93.75 % y con 0 % (recorrer una matriz por filas y por columnas), y en «Cuando la RAM se acaba», con 99 %, 100 de acierto (la RAM) y 100000 de fallo (un SSD).

Mueve los aciertos y mira cuánto pesan los pocos fallos.

La escalera completa

Ya conoces todos los pisos. Juntos forman la jerarquía de memoria (memory hierarchy). Arriba, los registros: unas decenas de casillas dentro de cada núcleo, con los números que está usando en este instante. Luego, las cachés, la RAM y, abajo, los discos. Cada piso hacia abajo guarda más y cuesta menos por byte, pero tarda más.

La escalera de memoria, en barras que miden cuánto tarda en llegar un dato, en una escala donde cada línea punteada vale 100 veces la anterior: 1 nanosegundo, 100 nanosegundos, 10 microsegundos y 1 milisegundo. A la derecha de cada piso, el mismo tiempo si 1 nanosegundo durara 1 segundo. Caché L1, de 32 a 128 KiB por núcleo: cerca de 1 ns, como 1 segundo. Caché L2, de 1 a 16 MiB: cerca de 4 ns, 4 segundos. Caché L3, decenas de MiB: cerca de 15 ns, 15 segundos. RAM, de 8 GB a varios TB: cerca de 100 ns, 1 minuto 40 segundos. SSD NVMe, decenas de TB: cerca de 0.1 ms, 28 horas. Disco de red EBS gp3: cerca de 1 ms, 12 días. Disco duro: cerca de 10 ms, 4 meses. Las tres cachés están dentro de la CPU; la RAM, fuera del chip; los discos guardan aunque se apague la computadora. LA ESCALERA DE MEMORIA la barra: cuánto tarda en llegar un dato cada línea punteada vale 100 veces la anterior 1 ns 100 ns 10 µs 1 ms L1 · 32–128 KiB por núcleo ≈ 1 ns → 1 s L2 · 1–16 MiB ≈ 4 ns → 4 s L3 · decenas de MiB ≈ 15 ns → 15 s RAM · 8 GB a varios TB ≈ 100 ns → 1 min 40 s SSD NVMe · decenas de TB ≈ 0.1 ms → 28 h Disco de red · EBS gp3 ≈ 1 ms → 12 días Disco duro · HDD ≈ 10 ms → 4 meses cachés: dentro de la CPU RAM: fuera de la CPU, se borra al apagar discos: lo que se queda aunque se apague
La escalera de memoria, de la más rápida a la más lenta. Cada línea punteada vale 100 veces la anterior: de la L1 al disco duro hay siete órdenes de magnitud, diez millones de veces. A la derecha, lo mismo a escala humana, como si 1 ns durara 1 segundo.

Los nanosegundos no se sienten; estíralos como en la figura:

La escalera, si 1 ns durara 1 segundoLa cuenta
L1
1 ns × 10⁹
1 s
L2
4 ns × 10⁹
4 s
L3
15 ns × 10⁹
15 s
RAM
100 ns × 10⁹
100 s = 1 min 40 s
SSD NVMeleer 4 KiB al azar
100 µs × 10⁹
100,000 s ≈ 28 h
Disco de redEBS gp3: un disco que AWS conecta por la red
1 ms × 10⁹
10⁶ s ≈ 12 días
Disco duro
10 ms × 10⁹
10⁷ s ≈ 4 meses
Resultado
Del primer piso al último, el mismo dato pasa de un segundo a cuatro meses: diez millones de veces más.
Para ir más hondoLa escalera de la GPU

La GPU tiene su propia escalera. Arriba están sus registros y una memoria pegada a cada grupo de núcleos, de decenas o cientos de KB; luego su caché L2 (4 MB en la T4) y, abajo, la VRAM, con su ancho de banda de 320 GB/s en la T4. Los tiles de la lección de FLOPs son la manera de aprovechar esa escalera. Entre la escalera de la CPU y la de la GPU hay un puente, PCIe, más angosto que cualquiera de las dos memorias.

03

Discos por dentro: disco duro, SSD y discos de la nube

Extra

Inside disks: hard drives, SSDs and cloud disks

Por qué un disco duro tarda

Un disco duro guarda los bits en platos magnéticos que giran sin parar, casi siempre a 7,200 vueltas por minuto (rpm). Encima flota un brazo con una cabeza lectora. Los datos van en círculos, las pistas. Para leer un dato en un lugar al azar hay que esperar dos cosas:

  1. 1

    Mover el brazo

    La cabeza viaja hasta la pista del dato. Se llama seek y, en un disco de 7,200 rpm, tarda en promedio unos 8 ms (los fabricantes dan de ≈ 7.5 a 8.5 ms).

  2. 2

    Esperar el giro

    La cabeza ya está en la pista, pero el dato puede venir del otro lado del plato. En promedio hay que esperar media vuelta.

Una lectura al azar en un disco duro de 7,200 rpmLa cuenta
Una vuelta
60 s ÷ 7,200
8.33 ms
Media vueltala espera promedio
8.33 ÷ 2
≈ 4.2 ms
Mover el brazoel seek promedio
dato del fabricante
≈ 8 ms
Una lectura al azar
8 + 4.2
≈ 12 ms
Lecturas al azar por segundo
1 s ÷ 12.2 ms
≈ 80
Leer 1 GB a saltos de 4 KiB10⁹ B ÷ 4,096 B = 244,141 lecturas
244,141 × 12.2 ms
≈ 50 minutos
Leer 1 GB de corrido≈ 250 MB/s, ya en posición
1 GB ÷ 250 MB/s
≈ 4 s
Resultado
El mismo GB tarda 4 segundos de corrido y casi una hora a saltos. Un disco duro es bueno leyendo seguido y pésimo saltando.
Por qué un disco duro tarda en leer 4 KiB en un lugar al azar. Arriba, un plato visto desde arriba, con sus pistas en círculos, el dato buscado marcado en naranja sobre su pista, también en naranja, y un brazo con la cabeza lectora, en azul, sobre otra pista. Paso 1: el brazo gira hasta poner la cabeza en la pista del dato (la posición nueva va con línea punteada), cerca de 8 milisegundos en promedio. Paso 2: hay que esperar a que el plato, que gira a 7,200 vueltas por minuto, lleve el dato hasta la cabeza: en promedio media vuelta, otros 4 milisegundos. En total, cerca de 12 ms por lectura al azar, unas 80 por segundo. En medio, un SSD: una tarjeta con dos chips de memoria flash y un chip de control, donde nada se mueve, que lee la celda directo en 0.05 a 0.1 ms. Abajo, las dos esperas a la misma escala: la barra del disco duro, 12 ms, cruza toda la figura; la del SSD, 0.1 ms, es una rayita; y la RAM, 0.0001 ms, mediría 0.0025 píxeles y no se ve. POR QUÉ UN DISCO DURO TARDA leer 4 KiB en un lugar al azar 1 2 plato: 7,200 rpm (vueltas por minuto) brazo 1 El brazo va a la pista del dato: ≈ 8 ms 2 Espera a que el dato llegue a la cabeza: media vuelta en promedio, ≈ 4 ms Total: ≈ 12 ms, unas 80 lecturas por segundo flash flash control SSD: nada se mueve. Lee la celda directo: ≈ 0.05 a 0.1 ms A la misma escala, leer 4 KiB al azar: disco duro ≈ 12 ms SSD NVMe ≈ 0.1 ms RAM ≈ 0.0001 ms: aquí mediría 0.0025 píxeles
Lo que espera un disco duro en cada lectura al azar: que el brazo lleve la cabeza a la pista del dato (la línea punteada) y que el giro traiga el dato hasta la cabeza. Abajo, las mismas esperas a escala: el SSD es una rayita, y la RAM ni se ve.

Hoy los discos duros viven donde importa el precio por TB y no la velocidad: respaldos, archivos viejos y el almacenamiento «frío» de las nubes.

La flash de un SSD

Un SSD guarda cada bit como una carga atrapada en una celda de memoria flash, que no se escapa aunque no haya luz. Se lee directo, sin mover nada: ≈ 0.05 a 0.1 ms por lectura al azar, más de 100 veces menos que un disco duro.

Tiene sus mañas. La flash se lee y se escribe por páginas propias, de unos KiB, pero solo se puede borrar por bloques grandes, de muchas páginas: reescribir un dato obliga al SSD a mover otros y a borrar después. Y cada celda aguanta un número limitado de escrituras, del orden de mil a unos pocos miles: un SSD se gasta con el uso. Escribir sin parar, por ejemplo usándolo de swap, lo gasta más rápido.

SATA contra NVMe

La diferencia entre un SSD lento y uno rápido muchas veces no está en la flash, sino en cómo se conecta. SATA es la conexión que se diseñó a principios de los 2000 para discos duros: la CPU le puede dejar una sola fila de 32 pedidos, y su tope es de 6 Gb/s (gigabits por segundo). Como de cada 10 bits que viajan solo 8 son datos, quedan 600 MB/s, y ≈ 550 en la práctica. NVMe (Non-Volatile Memory Express) se diseñó para flash y va directo por PCIe: muchas filas de pedidos a la vez (el estándar permite hasta 65,535), de ≈ 3 a 14 GB/s en los más rápidos que se venden, según la generación de PCIe, y cientos de miles de lecturas al azar por segundo. Los NVMe que trae una máquina de la nube suelen dar ≈ 1 a 3 GB/s.

Tres números de un disco

Un disco se mide con tres números que no se deben mezclar:

  • Latencia: cuánto tarda una lectura. ≈ 12 ms un disco duro; ≈ 0.1 ms un SSD.
  • IOPS (input/output operations per second): cuántas lecturas o escrituras chicas, normalmente de 4 KiB, aguanta por segundo. ≈ 80 un disco duro; cientos de miles un NVMe.
  • Throughput: su ancho de banda, cuántos MB por segundo da leyendo de corrido. Es el nombre que usa AWS en sus discos.

Para un disco, «rápido» no dice nada si no dices para qué: para leer un archivo grande de corrido, o para saltar entre millones de pedacitos.

Los discos de la nube

En AWS hay dos tipos de disco para una máquina:

  • Local (instance store): un NVMe pegado a la máquina, rápido. Pero se borra cuando la máquina se detiene: sirve para copias de trabajo, no para guardar. La g4dn.xlarge, la máquina con una T4 de C01, trae ≈ 125 GB.
  • De red (EBS, Elastic Block Store): un disco que vive en otro lado del centro de datos y se conecta por la red. Sobrevive a la máquina, pero cada lectura cruza la red, con una latencia de un dígito de milisegundos. El más común, gp3, da de base 3,000 IOPS y 125 MiB/s (≈ 131 MB/s); pagando más, hasta 2,000 MiB/s.

El gp3 es el disco raíz típico (el disco con el que arranca la máquina) de los nodos de Kubernetes en AWS, y sus 125 MiB/s de base son justo el número que la Ronda (M05) señala como el cuello de botella al bajar imágenes y pesos.

04

Localidad: por qué recorrer en orden es más rápido

Esencial

Locality: why going in order is faster

Las cachés funcionan por una razón: los programas no leen la memoria al azar. Tienen localidad (locality), de dos tipos:

  • Temporal: lo que usaste hace un momento lo vas a volver a usar pronto. El contador de un ciclo, los promedios con los que se normaliza cada canal de una foto, los pesos de una capa que se aplican a las 32 fotos de un batch.
  • Espacial: lo que está junto a lo que usaste lo vas a usar enseguida. El siguiente píxel, el siguiente número de una lista.

La caché aprovecha las dos: guarda lo reciente y trae líneas completas. Además, la CPU tiene un prefetcher, un circuito que detecta cuando recorres la memoria en orden y trae las siguientes líneas antes de que las pidas.

Una matriz se guarda fila tras fila

La memoria es una sola fila de casillas, así que una tabla de dos dimensiones hay que aplanarla. NumPy (la librería de Python para arreglos de números) y PyTorch, como el lenguaje C, guardan una fila completa y luego la siguiente: es el orden row-major («por filas»). En una matriz de float32 con 1,024 columnas, el número de la fila i y la columna j está en:

dirección = inicio + (i × 1,024 + j) × 4 bytes

El de la fila 2 y la columna 5, por ejemplo, está (2 × 1,024 + 5) × 4 = 8,212 bytes después del inicio. Recorrer por filas es avanzar de 4 en 4 bytes: cada línea trae 16 números seguidos. Recorrer por columnas es brincar 1,024 × 4 = 4,096 bytes en cada paso, una fila entera: cada lectura cae en una línea distinta. Ojo: la RAM sí tarda casi lo mismo con cualquier dirección; lo que cambia es cuántas veces hay que ir a ella.

Una matriz de 4 filas por 8 columnas guardada fila tras fila. Arriba, cómo queda en la memoria: la fila 0, luego la 1, la 2 y la 3, una detrás de otra, en bloques de 4 números; cada bloque es una línea de caché (en una CPU real, una línea trae 16 números float32). En medio, la matriz recorrida por filas, con el orden de lectura del 1 al 32: solo la primera lectura de cada línea es un fallo que va a la RAM; las otras tres ya llegaron con ella. Son 8 fallos en 32 lecturas. Abajo, la misma matriz recorrida por columnas: cada lectura cae en una línea distinta, y si la matriz no cabe en la caché, cuando el recorrido vuelve por la siguiente columna esa línea ya se fue. Las 32 lecturas son fallos. UNA MATRIZ DE 4 × 8, FILA TRAS FILA en la memoria va una fila detrás de otra: fila 0 fila 1 fila 2 fila 3 cada bloque es una línea de caché: 4 números en el dibujo, 16 float32 en una CPU real Por filas: 1 fallo cada 4 lecturas 1234 5678 9101112 13141516 17181920 21222324 25262728 29303132 Por columnas: cada lectura, otra línea si la matriz no cabe en la caché, al volver ya no está 1591317212529 26101418222630 37111519232731 48121620242832 fallo: a la RAM, ≈ 100 ns acierto: ≈ 1 ns
La misma matriz, recorrida de dos maneras. Por filas, cada línea que llega trae los siguientes 3 números (15 en una CPU real). Por columnas, cada lectura pide una línea nueva, y si la matriz no cabe en la caché, la línea ya se fue cuando el recorrido vuelve por ella.
Recorrer una matriz de 1,024 × 1,024 float32 con una caché de 32 KiBLa cuenta
Bytes de la matriz
1,048,576 × 4 B
4 MiB
Por filas: líneas que llegan64 B cada una
4 MiB ÷ 64 B
65,536 fallos
Aciertos por filas
1 − 65,536 ÷ 1,048,576
93.75 %
Por columnasla caché guarda 32 KiB ÷ 64 B = 512 líneas, y cada columna toca 1,024: al volver, ya se fueron
1,048,576 lecturas
1,048,576 fallos
Promedio por filas15 de cada 16 aciertan: 1 ns por acierto, 100 ns por fallo
15/16 × 1 + 1/16 × 100
≈ 7.19 ns
Tiempo por filas
1,048,576 × 7.19 ns
≈ 7.5 ms
Tiempo por columnas
1,048,576 × 100 ns
≈ 105 ms
Resultado
Los mismos números, leídos en otro orden: 16 veces más fallos y ≈ 14 veces más tiempo.

Este simulador cuenta los fallos con una caché de juguete de 32 KiB, que saca la línea que lleva más tiempo sin usarse:

python
from collections import OrderedDict

LINEA = 64                       # bytes por línea de caché
LINEAS_EN_CACHE = 512            # una caché de juguete: 512 × 64 B = 32 KiB
FILAS = COLUMNAS = 1024          # una matriz de float32: 1024 × 1024 × 4 B = 4 MiB

def recorrer(por_filas):
    cache, fallos = OrderedDict(), 0
    for a in range(FILAS):
        for b in range(COLUMNAS):
            i, j = (a, b) if por_filas else (b, a)
            direccion = (i * COLUMNAS + j) * 4        # fila tras fila, 4 bytes por número
            linea = direccion // LINEA
            if linea in cache:
                cache.move_to_end(linea)              # acierto: la línea ya estaba
            else:
                fallos += 1                           # fallo: hay que traerla de la RAM
                cache[linea] = True
                if len(cache) > LINEAS_EN_CACHE:
                    cache.popitem(last=False)         # sale la que lleva más tiempo sin usarse
    return fallos

lecturas = FILAS * COLUMNAS
for nombre, por_filas in (("por filas", True), ("por columnas", False)):
    f = recorrer(por_filas)
    print(f"{nombre:>12}: {f:>9,} fallos de {lecturas:,} lecturas · aciertos {1 - f / lecturas:.2%}")
Salida
   por filas:    65,536 fallos de 1,048,576 lecturas · aciertos 93.75%
por columnas: 1,048,576 fallos de 1,048,576 lecturas · aciertos 0.00%

Tu CPU tiene tres niveles y un prefetcher, así que los números de verdad son otros, pero la idea es la misma. Mídelo con una lista de 3 millones de números, sumada en orden y en desorden:

python
import random, time

N = 3_000_000
datos = list(range(N))                 # 3 millones de números, creados en orden
orden = list(range(N))                 # los lugares en orden: 0, 1, 2…
desorden = orden[:]
random.seed(7)
random.shuffle(desorden)               # los mismos lugares, revueltos

def sumar(lugares):
    inicio = time.perf_counter()
    total = 0
    for i in lugares:
        total += datos[i]
    return total, time.perf_counter() - inicio

suma1, t_orden = sumar(orden)
suma2, t_desorden = sumar(desorden)
print("¿las dos sumas dan lo mismo?", suma1 == suma2)
print("¿en desorden tardó más del doble?", t_desorden > 2 * t_orden)
Salida
¿las dos sumas dan lo mismo? True
¿en desorden tardó más del doble? True

Los tiempos cambian con la máquina y con la corrida; por eso el código solo imprime si pasó. En la Mac donde se escribió esta lección, el desorden tardó de 5 a 12 veces más. La razón: en Python, cada número es un objeto aparte y la lista guarda dónde está cada uno. Como se crearon en orden, quedaron uno tras otro; en desorden, cada salto cae en otra línea, lejos.

Dónde lo vas a ver en ML

  • CHW contra HWC. Pillow entrega la foto en HWC (los tres colores de cada píxel juntos) y el modelo la quiere en CHW. Transponer en PyTorch (permute) no mueve ni un byte: solo cambia el orden en que se van a leer. El tensor queda «no contiguo» (x.is_contiguous() da False), y algunas operaciones primero lo copian en orden. Haz esa copia una vez, con .contiguous(), y no en cada paso.
  • NumPy contra una lista. Un arreglo de NumPy guarda sus números juntos, 4 u 8 bytes cada uno; una lista de Python guarda direcciones de objetos regados por la memoria. Es parte de por qué NumPy es tan rápido.
  • El batch. Es localidad temporal en la GPU: cada peso que se trae de la VRAM se usa con todas las fotos del batch antes de soltarlo.
  • Archivos chicos contra grandes. En un disco, leer un archivo grande de corrido le gana a saltar entre millones de archivos chicos. Por eso los datasets grandes se empaquetan en archivos de cientos de MB (shards).
05

Memoria virtual: cada proceso ve su propio mapa

Esencial

Virtual memory: each process sees its own map

En una computadora corren muchos procesos a la vez, los programas en marcha de la lección anterior, y todos comparten la misma RAM. Si cada uno escribiera directo en las direcciones de verdad, un error en uno podría pisar la memoria de otro, y cada programa tendría que averiguar qué casillas están libres.

La solución es la memoria virtual (virtual memory). Cada proceso ve su propio espacio de direcciones, que empieza en 0 y llega hasta un número enorme, como si tuviera la máquina para él solo. Son direcciones virtuales. El sistema operativo, el programa que administra la máquina y reparte la RAM entre los procesos, las traduce a direcciones de verdad con ayuda de una pieza de la CPU, la MMU (memory management unit).

Páginas y tabla de páginas

Traducir byte por byte sería carísimo, así que se traduce por bloques, las páginas (pages): 4 KiB en casi todo Linux y 16 KiB en las Mac con chip de Apple. Cada proceso tiene una tabla de páginas que dice, para cada página suya, en qué marco (frame) de la RAM de verdad está. Dos procesos pueden usar la misma dirección virtual y caer en marcos distintos: nunca se pisan.

Un ejemplo con la figura de abajo y páginas de 4 KiB (4,096 bytes). El proceso A lee su dirección virtual 4,100. La CPU divide: 4,100 ÷ 4,096 da 1 y sobran 4, así que es el byte 4 de su página 1. La tabla de A dice que esa página está en el marco 2, y el byte de verdad está en 2 × 4,096 + 4 = 8,196. Si B lee la misma dirección, 4,100, cae en su propia página 1, que todavía no tiene marco.

Dos procesos, A y B, cada uno con sus páginas de 4 KiB numeradas desde 0, y en medio los marcos de 4 KiB de la RAM de verdad. Las flechas son la tabla de páginas de cada proceso. La página 0 de A, su código, está en el marco 0. La página 1 de A, la foto, en el marco 2. La página 2 de A, los pesos, en el marco 3. La página 3 de A, poco usada, se mandó al swap, en el disco. La página 0 de B, su código, está en el marco 1. La página 1 de B está reservada y todavía no tiene RAM. La página 2 de B, los pesos, también apunta al marco 3: los dos procesos comparten esa copia. La página 3 de B, un tensor, está en el marco 4. En el disco están el swap y el archivo de pesos del que se llenó el marco 3. A y B usan las mismas direcciones sin pisarse. CADA PROCESO, SU PROPIO MAPA las flechas son la tabla de páginas de cada uno Proceso A RAM de verdad Proceso B páginas de 4 KiB marcos de 4 KiB páginas de 4 KiB página 0código página 1la foto página 2pesos página 3poco usada marco 0código de A marco 1código de B marco 2foto de A marco 3pesos: A y B marco 4tensor de B página 0código página 1sin RAM aún página 2pesos página 3tensor Disco · sobrevive al apagar swap: ahí espera la página 3 de A pesos.safetensors: de ahí se llenó el marco 3 A y B usan las mismas direcciones sin pisarse. El marco 3 está en los dos mapas: una sola copia.
Dos procesos con sus propios mapas. Cada flecha es una línea de su tabla de páginas: una página puede estar en un marco de la RAM, compartida con otro proceso, en el disco o en ningún lado todavía. La página 1 de A, en el marco 2, es la del ejemplo.

Ese mapa le da al sistema operativo cuatro poderes:

  • Aislar: un proceso no puede ni ver la memoria de otro. Si truena, no corrompe a los demás.
  • Reservar sin entregar: cuando un programa pide 1 GB, el sistema anota la reserva y no le da ni un marco. La RAM llega de página en página, la primera vez que el programa escribe en cada una.
  • Compartir: dos procesos pueden apuntar al mismo marco. Así se comparten las librerías y los archivos abiertos con mmap, que ves en la siguiente sección.
  • Mandar páginas al disco: si falta RAM, una página poco usada se puede guardar en el disco para liberar su marco. Es el swap, dos secciones adelante.

El segundo poder explica algo pendiente de la lección anterior: por qué Linux casi siempre dice que sí cuando un proceso pide memoria, aunque no la tenga. Se llama overcommit («prometer de más»). La cuenta llega después, cuando el proceso escribe y cada página necesita su marco. Si entonces no hay RAM, ya no hay a quién decirle que no, y actúa el OOM killer.

Fallos de página

¿Qué pasa cuando un proceso toca una página sin marco, como B con su página 1? La CPU lo detiene y avisa al sistema operativo: es un fallo de página (page fault). No es un fallo de caché: aquel lo resuelve el hardware solo, yendo a la RAM; este lo resuelve el sistema operativo. El sistema consigue la página, actualiza la tabla y deja seguir al proceso. Si basta con darle un marco nuevo lleno de ceros, cuesta del orden de un microsegundo. Si hay que leer la página del disco, de 0.1 a 10 ms. El mismo acceso a la memoria puede tardar 100 ns o 10 ms, según dónde esté la página: es la respuesta completa a la pregunta de esta lección.

Lo que de verdad ocupa un proceso: la RSS

Por todo esto, el tamaño virtual de un proceso dice poco: puede reservar decenas de GB sin ocupar casi nada. Lo que importa es su RSS (resident set size): cuánta de su memoria está en la RAM en este momento. En ps o top, esa es la columna RSS (o RES); VSZ (o VIRT) es el tamaño virtual. Compruébalo. El código le pide memoria al sistema con mmap.mmap(-1, …), la forma más directa desde Python: el -1 quiere decir «sin archivo». Con un archivo, es el mmap de la siguiente sección.

python
import mmap, resource, sys

def ram_mb():                          # la RSS más alta que ha tenido este proceso, en MB
    r = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
    return r / 1e6 if sys.platform == "darwin" else r * 1024 / 1e6   # macOS la da en bytes; Linux, en KiB

TAM = 400_000_000                      # 400 MB
antes = ram_mb()
m = mmap.mmap(-1, TAM)                 # le pide al sistema 400 MB de memoria
print(f"pedí 400 MB:                   la RAM creció ≈ {ram_mb() - antes:.0f} MB")
m[0] = 1                               # escribe un solo byte
print(f"escribí 1 byte:                la RAM creció ≈ {ram_mb() - antes:.0f} MB")
saltos = range(0, TAM, 4096)
for i in saltos:
    m[i] = 1                           # un byte cada 4 KiB
print(f"escribí {len(saltos):,} bytes sueltos: la RAM creció ≈ {ram_mb() - antes:.0f} MB")
Salida
pedí 400 MB:                   la RAM creció ≈ 0 MB
escribí 1 byte:                la RAM creció ≈ 0 MB
escribí 97,657 bytes sueltos: la RAM creció ≈ 400 MB

Pedir no ocupó nada. Escribir 97,657 bytes, menos de 0.1 MB, ocupó 400 MB, porque cada byte cayó en una página distinta y cada página se entrega completa. Dos avisos. resource solo existe en macOS y Linux: en Windows, este bloque no corre. Y en una Mac las páginas son de 16 KiB, así que escribir cada 4 KiB toca cada página cuatro veces, con el mismo resultado.

Para ir más hondoLa TLB, las páginas grandes y la memoria fijada

Traducir cada dirección pediría leer la tabla de páginas, que está en la RAM: otra vez 100 ns. Para no hacerlo, la CPU guarda las traducciones recientes en una caché propia, la TLB (translation lookaside buffer), de unos cientos a unos miles de traducciones. Con páginas de 4 KiB, 1,500 traducciones cubren ≈ 6 MiB; con páginas grandes (huge pages) de 2 MiB, ≈ 3 GiB. Por eso las usan las bases de datos y otros programas que manejan mucha memoria.

Como el sistema puede mover las páginas o mandarlas al swap, la GPU, que copia de la RAM por su cuenta, necesita memoria fijada (pinned), que el sistema promete dejar quieta: lo ves en «CPU y GPU».

06

Cargar el modelo: la page cache y mmap

Esencial

Loading the model: the page cache and mmap

La page cache: la RAM libre, trabajando

La RAM libre no se queda de brazos cruzados. Cada vez que un programa lee un archivo, el sistema operativo se queda con una copia en la RAM que nadie está usando: la page cache. La segunda vez que alguien lee ese archivo, sale de la RAM y no del disco. Si un programa necesita la memoria, el sistema suelta esa copia sin preguntar: no estorba.

Por eso, en un servidor Linux, el comando free casi siempre muestra poca memoria «free» y mucha en «buff/cache». No es un problema: la columna que importa es «available», lo que de verdad hay si alguien lo pide. La confusión es tan común que tiene su propia página web: «Linux ate my RAM» («Linux se comió mi RAM»).

Las escrituras también pasan por la page cache y llegan al disco segundos después. Por eso hay que «expulsar» una memoria USB antes de sacarla: expulsar obliga a escribir lo pendiente.

mmap: abrir un archivo como si fuera memoria

Hay dos maneras de usar un archivo. La normal es leerlo (read): el sistema lo trae a la page cache y lo copia a la memoria del proceso, así que queda dos veces en la RAM. La otra es mmap (memory map): el proceso pide que el archivo aparezca en sus direcciones virtuales, y el sistema apunta esas páginas a la page cache, sin copiar nada. Los bytes no se leen del disco hasta que el programa toca cada página: es un fallo de página de los que leen del disco.

Eso le da tres ventajas a quien carga un modelo:

  • Abrir no cuesta: un archivo de 100 GB se «abre» al instante; solo se lee lo que se toca.
  • Una sola copia en la RAM: la de la page cache.
  • Se comparte: dos procesos que abren el mismo archivo usan los mismos marcos.

Para eso existe el formato safetensors: un encabezado en JSON (un formato de texto con nombres y valores, como {"dtype": "F32"}) que dice dónde está cada tensor y, después, los números tal cual, sin nada que interpretar. Además, abrirlo no puede ejecutar código escondido, cosa que sí puede pasar con un pickle, el formato que usa PyTorch si no le pides otro. torch.load(..., mmap=True) abre con mmap los archivos de PyTorch, y vLLM, el programa que sirve el LLM de C10, lee sus safetensors con mmap. Este bloque arma un archivo de «pesos» al estilo safetensors y lo abre así:

python
import json, mmap, os, resource, struct, sys, tempfile

def ram_mb():                          # la RSS más alta del proceso, en MB, como en la sección anterior
    r = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
    return r / 1e6 if sys.platform == "darwin" else r * 1024 / 1e6

n = 50_000_000                         # 50 millones de pesos float32: 200 MB
encabezado = json.dumps({"capa.peso": {"dtype": "F32", "shape": [n], "data_offsets": [0, 4 * n]}}).encode()
ruta = os.path.join(tempfile.mkdtemp(), "modelo.safetensors")
with open(ruta, "wb") as f:            # el largo del encabezado, el encabezado y los números
    f.write(struct.pack("<Q", len(encabezado)) + encabezado)
    for _ in range(200):
        f.write(struct.pack("<f", 0.5) * 250_000)       # 1 MB de pesos que valen 0.5

antes = ram_mb()
with open(ruta, "rb") as f:
    m = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)  # los 200 MB aparecen en la memoria
largo = struct.unpack("<Q", m[:8])[0]
info = json.loads(m[8:8 + largo])["capa.peso"]
pesos = memoryview(m)[8 + largo:].cast("f")            # los números, sin copiarlos
print(f"abrí {len(m) / 1e6:.0f} MB · tensor {info['dtype']} de {info['shape'][0]:,} números")
print(f"el peso del medio vale {pesos[n // 2]} · la RAM creció ≈ {ram_mb() - antes:.0f} MB")
print(f"todos suman {sum(pesos):,.0f} · la RAM creció ≈ {ram_mb() - antes:.0f} MB")
pesos.release(); m.close(); os.remove(ruta)
Salida
abrí 200 MB · tensor F32 de 50,000,000 números
el peso del medio vale 0.5 · la RAM creció ≈ 0 MB
todos suman 25,000,000 · la RAM creció ≈ 200 MB

Leer el encabezado y un peso del medio tocó dos páginas: la RAM ni se movió. Sumar todo tocó los 200 MB. Un detalle honesto: como el archivo se acababa de escribir, sus páginas ya estaban en la page cache y no se leyó nada del disco. Con un archivo «frío», cada página que tocas espera al disco.

El arranque en frío

Ya puedes seguir los pesos de un LLM desde que el nodo nace hasta que responde. Es el arranque en frío (cold start): una máquina nueva, sin nada en su disco ni en su page cache. Los pesos vienen de S3, el almacén de archivos de AWS, y pasan por el disco de la máquina: un NVMe local, pegado a ella, o el disco de red con el que arranca, un gp3, que de base lee 125 MiB/s.

Cargar los pesos al arrancar: del almacén a la GPU · Sigue «Primer arranque en un nodo nuevo» y compáralo con «Reinicio en el mismo nodo». «Con una lectura normal» muestra la copia de más que evita mmap.
  • S3 · los pesos de cada versión
    El almacén de archivos de AWS, del otro lado de la red. Guarda los pesos de cada versión del modelo; un nodo los baja a ≈ 0.2 a 1 GB/s, según la Ronda (M05).
  • Disco local · NVMe o gp3
    El disco del nodo. Un NVMe local lee ≈ 1 a 3 GB/s; el gp3, el disco de red con el que arranca la máquina en AWS, de base, 125 MiB/s: con él, cargar 16 GB tarda ≈ 2 minutos.
  • Page cache · el archivo, en RAM
    La page cache: la copia que el sistema operativo guarda en RAM de cada archivo que se lee. La segunda vez, el archivo sale de aquí y no del disco.
  • Proceso · vLLM, Triton o tu API
    El servidor que carga el modelo. Si lee el archivo completo a su propia memoria, con read o un torch.load normal, guarda una copia más; con mmap usa directo las páginas de la page cache.
  • VRAM · los pesos, listos
    Donde tienen que acabar los pesos para que la GPU calcule. Se copian por PCIe: 16 GB a ≈ 16 a 32 GB/s, del orden de un segundo.
Cargar Llama 3 8B en BF16, 16.06 GBLa cuenta
Los pesos8,030 millones de parámetros de 2 bytes
8.03 × 10⁹ × 2 B
16.06 GB
Desde S3, por la redM05: ≈ 0.2 a 1 GB/s por nodo
16.06 GB ÷ (0.2 a 1 GB/s)
≈ 16 a 80 s
Desde un disco gp3 de base125 MiB/s ≈ 131 MB/s
16.06 GB ÷ 131 MB/s
≈ 123 s ≈ 2 min
Desde un NVMe local≈ 2 GB/s
16.06 GB ÷ 2 GB/s
≈ 8 s
Desde la page cacheun reinicio en el mismo nodo
sin tocar el disco
segundos
A la VRAM, por PCIe≈ 16 a 32 GB/s
16.06 GB ÷ (16 a 32 GB/s)
≈ 0.5 a 1 s
Resultado
El disco decide: el mismo modelo tarda 2 minutos u 8 segundos en salir del disco, y casi nada si ya estaba en la page cache. Cruzar a la GPU es lo de menos.
Cuánto tarda en cargar un modeloCalculadora

Antes de responder, los pesos tienen que salir del disco. Lo que ya está en la page cache sale de la RAM y casi no cuenta. Arranca con Llama 3 8B en BF16 leído de un disco gp3 de base (125 MiB/s ≈ 131 MB/s).

Bytes que hay que leer del disco16.1 GB
Tiempo leyendo del disco122.60 s
En minutos2.0
Contra un NVMe local a 2,000 MB/sveces más lento (menos de 1×: más rápido)15.3×

Escribe 2000 en el disco: el mismo modelo baja a ≈ 8 s. Luego pon 100 % de page cache: es un reinicio en el mismo nodo, y el disco ya no cuenta. Prueba 141 GB con 131 MB/s: casi 18 minutos solo de leer.

Prueba también el engine de C01, 0.15 GB, con el gp3 de base: ≈ 1 s. A C01 le preocupa bajar la imagen de Triton, de varios GB, no su modelo.

En C10, que sirve un LLM propio con 70.6 GB de pesos, la Ronda pone el arranque completo en ≈ 5 a 10 minutos: conseguir la máquina, bajar la imagen, copiar los pesos de S3 al NVMe y cargarlos a las GPUs. Leerlos de un gp3 de base tardaría ≈ 9 minutos, contra ≈ 35 segundos del NVMe.

07

Cuando la RAM se acaba: swap y OOM

Esencial

When RAM runs out: swap and OOM

Ahora imagina que los procesos de una máquina necesitan más RAM de la que hay: no la que pidieron, sino la que de verdad usan. El sistema operativo tiene tres salidas, más o menos en este orden:

  1. Soltar la page cache. Es gratis: son copias de archivos que siguen en el disco.
  2. Mandar páginas al swap. El swap es un pedazo del disco reservado para guardar páginas de memoria. El sistema elige las que no se han usado en un rato, las escribe ahí y libera sus marcos.
  3. Matar un proceso. Es el OOM (out of memory) de la lección anterior: el sistema elige un proceso, casi siempre el que más memoria usa, y lo termina de golpe para recuperar su RAM.

El precio del swap

El swap evita matar a alguien, pero cobra. Cuando un proceso toca una página que se fue al swap, hay un fallo de página de los caros: el sistema la lee del disco. Desde un SSD tarda ≈ 100 µs, mil veces lo de la RAM; desde un disco duro, ≈ 10 ms, cien mil veces.

Un servidor que saca 1 de cada 100 lecturas del swapLa cuenta
99 lecturas de la RAM
99 × 100 ns
9,900 ns
1 lectura del swap, en un SSD
1 × 100 µs
100,000 ns
Promedio por lectura
(9,900 + 100,000) ÷ 100
≈ 1,099 ns
Comparado con todo en la RAM
1,099 ÷ 100
≈ 11 veces más lento
Con el swap en un disco duro
(9,900 + 10,000,000) ÷ 100
≈ 100,099 ns: ≈ 1,000 veces
Resultado
Basta 1 % de lecturas en el swap para que todo vaya 11 veces más lento. Es la cuenta de los aciertos de la caché, un piso más abajo: pruébala en la calculadora con 99 %, 100 y 100000.

Y puede ser peor. Si lo que los programas usan todo el tiempo no cabe en la RAM, el sistema saca una página, el programa la pide de vuelta, el sistema saca otra… y se pasa la vida moviendo páginas. Se llama thrashing («dar tumbos»): la máquina no se cae, pero se arrastra.

Laptop contra servidor

En una laptop, el swap vale la pena: es mejor que tus apps abiertas se pongan lentas un rato a que el sistema las cierre. macOS, además, comprime la memoria poco usada antes de mandarla al swap. En la Mac de 16 GB donde se escribió esta lección, sysctl vm.swapusage marcaba más de 5 GB de swap en uso.

En un servidor que responde requests es al revés. Un servidor lento es peor que uno caído: las requests se tardan, se amontonan, el p99 explota y nada da un error que avise. Uno caído se reinicia en segundos o se reemplaza. Por eso los servidores de producción casi siempre tienen el swap apagado. Kubernetes, tal como viene, ni siquiera arranca en una máquina con swap; las versiones recientes ya lo permiten, pero hay que activarlo a propósito en cada nodo.

El límite de un contenedor

En la lección anterior viste que un contenedor tiene su propio tope de memoria, que el sistema operativo hace cumplir con un cgroup: si sus procesos lo pasan, los mata aunque a la máquina le sobre RAM, y Kubernetes lo reporta como OOMKilled, con código de salida 137, el de la señal SIGKILL. En C01, el contenedor de Triton pide 6 GiB y tiene ese mismo límite: si se pasa, muere de golpe y se reinicia, en vez de ponerse lento.

Con lo de esta lección ya puedes decir qué cuenta contra ese tope: la memoria propia de sus procesos y las páginas de archivos que usan activamente, como unos pesos abiertos con mmap. La page cache que nadie usa se puede soltar, y no lo mata.

La GPU no tiene swap

En un servidor con Linux, la VRAM no tiene un piso de abajo. Si un tensor no cabe, PyTorch falla en el momento con «CUDA out of memory»: no se pone lento, truena. En Windows, en cambio, el driver de NVIDIA (el programa que maneja la GPU) puede usar la RAM como respaldo, y entonces se arrastra en vez de tronar.

08

La memoria de un contenedor: workers y memoria compartida

Extra

A container's memory: workers and shared memory

N workers, N copias

Un servidor de Python suele correr varias copias de sí mismo para usar varios núcleos: uvicorn --workers 4 (uvicorn es el programa que corre servidores de Python como el gateway) arranca 4 procesos, cada uno un worker (trabajador) que atiende requests. Cada uno tiene su propia memoria virtual. Si cada uno carga el modelo con un torch.load normal, hay 4 copias en la RAM. La Ronda lo advierte en M05: varios workers dentro de un contenedor «duplican la memoria».

Cuatro workers en un contenedor de 4 GiBLa cuenta
El límite4 GiB
4 × 1,073,741,824 B
≈ 4.29 GB
Memoria propia de cada workerPython y librerías
un supuesto
≈ 0.4 GB
Pesos del modelo≈ 300 millones de parámetros en FP32
300 M × 4 B
1.2 GB
Cada worker con su copia
4 × (0.4 + 1.2)
6.4 GB: OOMKilled
Con mmap, una sola copia
4 × 0.4 + 1.2
2.8 GB: cabe
Resultado
El mismo modelo y los mismos workers: con 4 copias no caben; con mmap caben y sobra un tercio del límite.
¿Caben los workers en el límite del contenedor?Calculadora

Cada worker (trabajador) es un proceso con su propia memoria. El modelo puede ir una vez por worker (cada uno lo lee con read o torch.load) o una sola vez para todos (lo abren con mmap y comparten la page cache). Arranca con el ejemplo del texto: 4 workers de 400 MB, un modelo de 1.2 GB y un límite de 4 GiB.

Memoria de todo el contenedor6.40 GB
El límitelos GiB pasados a GB4.29 GB
Parte del límite que ocupamás de 100 %: OOMKilled149.0 %
Suma de las RSS de los workerslo que darías sumando lo que marca top: con mmap, cuenta el modelo varias veces6.40 GB
Workers que caben, como máximo2

Con 1 (una copia por worker) son 6.4 GB: no caben en 4 GiB. Pon 0 (mmap): bajan a 2.8 GB y caben, aunque la suma de las RSS siga marcando 6.4 GB.

Busca cuántos workers caben con cada forma de cargar el modelo.

C01 evita el problema de raíz: corre un solo proceso de uvicorn por contenedor y, si hace falta más, Kubernetes agrega réplicas (M05). Con GPU hay otra razón: cada worker carga además su propia copia de los pesos en la VRAM y su propio contexto de CUDA, lo que el software de NVIDIA aparta en la GPU para cada proceso, de 0.3 a 1 GB cada uno. Ahí mmap no ayuda (M07).

Sumar la RSS engaña

Si haces top en ese contenedor con mmap, cada worker marca una RSS de ≈ 1.6 GB: su memoria propia más el modelo, que todos tocan. Sumarlas da 6.4 GB, más que el límite, y sin embargo caben. Las páginas compartidas aparecen en la RSS de cada uno, pero existen una sola vez. En Linux, la PSS (proportional set size) reparte cada página compartida entre quienes la usan, y ahí sí cuadra. Lo que mira el límite es el total del contenedor, no la suma de las RSS.

fork y copy-on-write

Hay otra forma de compartir. Si el servidor carga el modelo antes de crear a los workers (en gunicorn, otro programa como uvicorn, la opción --preload), cada worker nace como una copia del proceso original con fork, y el sistema no copia ni un marco: los mapas apuntan a los mismos. Solo cuando alguien escribe en una página, esa página se copia. Se llama copy-on-write («copiar al escribir»).

En Python tiene una trampa: cada objeto lleva un contador de cuántos lo usan, y actualizarlo es escribir, así que los objetos chicos terminan copiados de todos modos. Los pesos, que viven en bloques grandes de números que nadie modifica, sí se quedan compartidos.

/dev/shm: memoria compartida con nombre

Para pasarse datos grandes sin copiarlos, los procesos usan memoria compartida: un pedazo de RAM que varios mapean. En Linux se ve como una carpeta, /dev/shm, cuyos archivos viven en la RAM y no en el disco. El DataLoader de PyTorch, la pieza que arma los batches al entrenar, la usa cuando num_workers es mayor que 0: sus procesos preparan cada batch y lo dejan ahí. Triton, vLLM y NCCL, la librería con la que se comunican las GPUs, también la usan.

Docker le da a cada contenedor un /dev/shm de 64 MiB (≈ 67 MB). Un batch de 64 fotos de 3 × 224 × 224 en float32 pesa 38.5 MB, y con 4 workers que preparan 2 batches por adelantado cada uno, el DataLoader necesita del orden de 300 MB. Con 64 MiB truena con un mensaje famoso: «Unexpected bus error encountered in worker. This might be caused by insufficient shared memory (shm)».

La salida es agrandarlo: --shm-size=1g en Docker, shm_size: 1g en Docker Compose o, en Kubernetes, un volumen en memoria (emptyDir con medium: Memory) montado en /dev/shm, es decir, puesto en esa carpeta. Ojo: lo que se escribe ahí es RAM y cuenta contra el límite del contenedor.

09

Errores comunes

Esencial

Common mistakes

1. Leer «free» y creer que la máquina se quedó sin RAM

free marca casi nada en «free», y quien lo mira se asusta o pide máquinas más grandes. Es la page cache, que se suelta en cuanto alguien necesita memoria.

Cómo evitarlo: mira «available» y, en un contenedor, su working set, la memoria que de verdad usa. Lo que avisa que falta RAM es que empiece a usarse el swap o a morir procesos.

2. Medir el arranque en una máquina caliente

Reinicias el servidor en tu máquina y carga en 5 segundos. En producción, un nodo nuevo tarda minutos: su page cache está vacía y su disco es otro.

Cómo evitarlo: mide el arranque en un nodo nuevo, o vacía antes la page cache. En Linux, como root, el usuario con permiso para casi todo: sync; echo 3 > /proc/sys/vm/drop_caches (sync escribe lo pendiente y el echo suelta la page cache). Anota siempre de qué disco se leyó.

3. Dejar el swap encendido en un servidor de inferencia

Nada falla, pero la latencia sube de a poco y el p99 se dispara con más carga. Los health checks (las revisiones automáticas de que el servidor responde) pasan, así que nadie reinicia nada.

Cómo evitarlo: swap apagado en las máquinas que sirven, un límite de memoria en cada contenedor y una alerta cuando la memoria se acerca al límite. Mejor que muera rápido y se reinicie a que se arrastre.

4. Contar mal la memoria de los workers

Hay dos errores opuestos. Sumar la RSS de 4 workers que comparten un modelo abierto con mmap y concluir que no caben. O suponer que comparten el modelo cuando cada uno cargó su copia, y llevarse un OOMKilled.

Cómo evitarlo: mide el total del contenedor (lo que reporta Kubernetes, o en Linux la PSS, que reparte cada página compartida entre quienes la usan) y decide a propósito cómo se carga el modelo: mmap, --preload (cargarlo antes de crear a los workers) o un solo proceso por contenedor, como hace C01.

5. Recorrer los datos contra su orden

Un ciclo que va por columnas en una matriz guardada por filas, un tensor transpuesto que se usa miles de veces sin hacerlo contiguo, o millones de archivos chicos leídos al azar. El código es correcto y va varias veces más lento.

Cómo evitarlo: recorre en el orden en que están guardados los datos, haz .contiguous() una vez y no en cada paso, y empaqueta los archivos chicos en archivos grandes.

6. Leer los pesos de un disco de red lento

Los pesos de un LLM en el gp3 de base con el que arranca la máquina, a 125 MiB/s: los 141 GB de Llama 3 70B en BF16 (70,600 millones de parámetros de 2 bytes) tardan ≈ 18 minutos solo en leerse. Una réplica nueva, que se arrancó porque subió el tráfico, tarda esos 18 minutos de más en ayudar.

Cómo evitarlo: copia los pesos a un NVMe local, paga más throughput en el disco o bájalos al nodo antes de que se necesiten. Y si el modelo es chico, como el de C01, mételo en la imagen.

10

En la vida real

Extra

In real life

C01: el servidor de la T4

El servidor de GPU de C01 es una g4dn.xlarge: 4 vCPU (la unidad de CPU que renta la nube; en estas máquinas de Intel, cada uno es medio núcleo), 16 GiB de RAM, una T4 con 16 GB de VRAM y ≈ 125 GB de NVMe local. El engine de la ResNet-50, de ≈ 50 a 150 MB, va dentro de la imagen de Triton, y cargarlo de cualquier disco toma ≈ 1 s. Lo que tarda es bajar la imagen, de varios GB: ≈ 2 a 5 minutos, según la Ronda. El contenedor de Triton tiene 6 GiB de RAM, que se van sobre todo en las librerías de CUDA y los buffers (la memoria donde se acomodan los datos que van y vienen); el modelo vive en la GPU.

Y la foto de Ana nunca toca un disco. El gateway la decodifica en su RAM, pero antes de abrirla revisa su tamaño y rechaza las de más de 40 MP: una de 40 millones de píxeles abierta en RGB ocupa 40 M × 3 bytes = 120 MB.

C07: 51 GB servidos desde la page cache

C07 busca entre 100 millones de publicaciones. Cada máquina guarda en un disco de red gp3 los vectores en FP32 de su pedazo del índice, uno de 4: 25 millones de vectores de 512 números de 4 bytes, 51.2 GB. Con ellos revisa con precisión los 200 mejores candidatos de cada búsqueda. ¿Se pueden leer del disco en cada búsqueda?

Por qué los vectores de C07 tienen que estar en la page cacheLa cuenta
Búsquedas por máquina en el picocada búsqueda pasa por los 4 pedazos, y cada pedazo tiene 2 copias
1,750 ÷ 2 copias
≈ 875 por segundo
Lecturas al azar200 candidatos por búsqueda
875 × 200
175,000 por segundo
Lo que aguanta el disco de redel techo de EBS de esa máquina
dato de AWS
40,000 por segundo
Faltan
175,000 ÷ 40,000
≈ 4.4 veces
Precalentarleer los 51.2 GB de corrido, si el volumen da sus 125 MiB/s de base
51.2 GB ÷ 131 MB/s
≈ 6.5 minutos
Resultado
Del disco no salen. Por eso C07 usa máquinas de 128 GiB, donde los 51.2 GB caben en la page cache, y una copia nueva lee sus vectores de corrido antes de recibir tráfico. Un NVMe local quitaría el precalentamiento.

La Ronda lo dice con una frase que resume esta lección: dentro del índice, «un fallo de página en medio del grafo es un acceso a disco».

C10: 70.6 GB al NVMe, leídos con mmap

C10 sirve un LLM de 70,600 millones de parámetros en FP8, en máquinas con 8 H100 y 2 TiB de RAM. Al arrancar, un primer contenedor copia los 70.6 GB de pesos de S3 al NVMe local, en ≈ 1 a 2 minutos, y verifica que ningún archivo venga alterado; luego vLLM los abre con mmap. Por eso la memoria que pide cada servidor, 256 GiB, cuenta la page cache de esos archivos, además del /dev/shm y los buffers. Y por eso su /dev/shm es grande: con los 64 MiB de siempre, los procesos de vLLM no se pueden coordinar.

En la GPU, C10 vive al borde de la VRAM. Cada conversación ocupa su KV cache, la memoria donde el LLM guarda lo que ya calculó de ella, y cuando ya no caben, vLLM saca algunas y las recalcula después, cuando hay lugar: la persona ve una pausa y la GPU repite trabajo. Antes, vLLM también podía guardar esos bloques en la RAM de la CPU en vez de tirarlos, un «swap» un piso más arriba; la versión de hoy prefiere recalcular, porque le cuesta menos (M14). Por eso, dice la Ronda en C10, en un LLM la memoria es la capacidad: contar las conversaciones que se sacan avisa del problema antes que la latencia.

El Jetson y tu Mac: una sola memoria

En un Jetson, el equipo chico de NVIDIA para el edge (el cómputo junto a donde nacen los datos, como una cámara), la CPU y la GPU comparten la misma RAM: no hay VRAM aparte. Un Orin Nano de 8 GB, con el sistema y la app usando ≈ 2.5 GB, le deja al modelo ≈ 5.5 GB como máximo (M04). Las Mac con chip de Apple hacen lo mismo con su «memoria unificada»: la GPU de la M4 usa los mismos 16 GB que la CPU. La ventaja es que no hay copia por PCIe; el costo, que todo compite por la misma memoria y por el mismo ancho de banda.

Fuera de ML

  • Tu celular cierra las apps que dejaste atrás cuando le falta memoria: al volver a una, empieza desde cero. Es un OOM a propósito, para que la app que estás usando no se arrastre.
  • Redis, una base de datos que guarda todo en la RAM, responde en microsegundos, pero si se reinicia pierde lo que no haya escrito al disco. La Ronda (M02) lo apunta al elegir una cola: «durabilidad según cómo persista Redis».
  • Postgres, una de las bases de datos más usadas, guarda en su propia RAM las páginas de las tablas que más lee y deja el resto a la page cache del sistema. Su documentación sugiere darle para eso ≈ 25 % de la RAM de la máquina: la page cache hace lo demás.
  • Los videojuegos tienen pantallas de carga porque copian del disco a la RAM y a la VRAM. El SSD del PlayStation 5, de 5.5 GB/s, casi hizo desaparecer las que el PlayStation 4 sufría con su disco duro.
11

Ejercicios

Esencial

Exercises

Resuélvelos con lápiz antes de abrir la solución. Los de aciertos, carga y workers se pueden comprobar con las calculadoras.

  1. 1Fácil

    A escala humana (1 ns = 1 s), ¿cuánto tardan una lectura de la L2 (≈ 4 ns), una de la RAM (≈ 100 ns), una lectura al azar de un NVMe (≈ 80 µs) y una de un disco duro (≈ 12 ms)?

    Ver la solución paso a paso
    • L2: 4 ns × 10⁹ = 4 s.
    • RAM: 100 ns × 10⁹ = 100 s = 1 min 40 s.
    • NVMe: 80 µs = 80,000 ns → 80,000 s; entre 3,600 s por hora, ≈ 22 horas.
    • Disco duro: 12 ms = 12,000,000 ns → 12,000,000 s; entre 86,400 s por día, ≈ 139 días, unos 4 meses y medio.
  2. 2Fácil

    Una línea de caché mide 64 bytes. ¿Cuántos float32 trae? ¿Cuántos píxeles RGB en uint8? ¿Cuántas líneas ocupa una fila de 224 números float32?

    Ver la solución paso a paso
    • float32: 64 ÷ 4 = 16.
    • Un píxel RGB en uint8 son 3 bytes: 64 ÷ 3 = 21.3, así que 21 píxeles completos y un pedazo del siguiente.
    • Una fila de 224 float32: 224 × 4 = 896 bytes, y 896 ÷ 64 = 14 líneas.
  3. 3Fácil

    Se va la luz en el servidor de GPU de C01. ¿Qué se pierde y qué se queda: el engine en el disco, los pesos en la VRAM, el tensor de una foto a la mitad y una línea de log que el servidor acaba de escribir?

    Ver la solución paso a paso
    • Se queda el engine, que está en el disco.
    • Se pierden los pesos en la VRAM y el tensor, que estaban en la VRAM y en la RAM. Al volver, el servidor repite el arranque, y alguien tiene que reintentar esa request.
    • La línea de log, depende: si ya llegó al disco, se queda; si seguía en la page cache esperando a escribirse, también se pierde.
  4. 4Fácil

    El ONNX de C01 en FP32 pesa ≈ 98 MB. ¿Cuánto tarda en leerse de un disco gp3 de base (125 MiB/s) y de un NVMe a 2 GB/s? ¿Y los 141 GB de Llama 3 70B en BF16, del mismo gp3?

    Ver la solución paso a paso
    • gp3: 125 MiB/s = 125 × 1,048,576 ≈ 131 MB/s, y 98 ÷ 131 ≈ 0.75 s.
    • NVMe: 98 MB ÷ 2,000 MB/s ≈ 0.05 s.
    • Llama 3 70B: 141,000 MB ÷ 131 MB/s ≈ 1,076 s ≈ 18 minutos. Para un modelo chico, el disco no importa; para un LLM, decide el arranque.
  5. 5Medio

    Una lectura acierta en la L1 (1 ns) el 98 % de las veces y las demás van a la RAM (100 ns). ¿Cuánto tarda en promedio? ¿Y con 90 %? ¿Qué parte del tiempo se va en fallos en cada caso?

    Ver la solución paso a paso
    • 98 %: 0.98 × 1 + 0.02 × 100 = 0.98 + 2 = 2.98 ns. Los fallos se llevan 2 ÷ 2.98 ≈ 67 % del tiempo.
    • 90 %: 0.9 × 1 + 0.1 × 100 = 0.9 + 10 = 10.9 ns, casi 11 veces lo de un acierto. Los fallos se llevan 10 ÷ 10.9 ≈ 92 %.
    • Bajar de 98 % a 90 % de aciertos hace cada lectura ≈ 3.7 veces más lenta.
  6. 6Medio

    Una imagen en escala de grises de 2,000 × 2,000 píxeles en float32 se guarda por filas, y una caché de 32 KiB la recorre. ¿Cuántos bytes ocupa? ¿Cuántos fallos hay por filas? ¿Y por columnas, si cada lectura cae en una línea que ya se fue? ¿Cuánto tarda cada recorrido con 1 ns por acierto y 100 ns por fallo?

    Ver la solución paso a paso
    • Bytes: 2,000 × 2,000 × 4 = 16,000,000 B, 16 MB: no cabe en 32 KiB.
    • Por filas: 16,000,000 ÷ 64 = 250,000 fallos, uno cada 16 lecturas.
    • Por columnas: cada una de las 4,000,000 lecturas es un fallo: 4,000,000, 16 veces más.
    • Tiempo por filas: cada lectura promedia 15/16 × 1 + 1/16 × 100 ≈ 7.19 ns, y 4,000,000 × 7.19 ns ≈ 29 ms. Por columnas: 4,000,000 × 100 ns = 400 ms.
  7. 7Medio

    En el código de la memoria virtual, cambia el 4096 por 16384: un byte cada 16 KiB. ¿Cuánto crece la RAM en Linux, con páginas de 4 KiB? ¿Y en una Mac con chip de Apple, con páginas de 16 KiB?

    Ver la solución paso a paso
    • Escribes 400,000,000 ÷ 16,384 ≈ 24,414 bytes, uno cada 16 KiB.
    • Linux, páginas de 4 KiB: cada byte cae en una página distinta, pero solo tocas 1 de cada 4 páginas. Crece ≈ 100 MB.
    • Mac, páginas de 16 KiB: tocas todas las páginas. Crece ≈ 400 MB, igual que antes.
    • La memoria se entrega de página en página: no cuentan los bytes que escribes, sino cuántas páginas tocas.
  8. 8Medio

    Un servidor saca de la RAM (100 ns) el 99.9 % de sus lecturas y el 0.1 % del swap en un SSD (100 µs). ¿Cuánto tarda una lectura en promedio, y cuántas veces más que si todo estuviera en la RAM? ¿Y con el swap en un disco duro (10 ms)?

    Ver la solución paso a paso
    • SSD: 0.999 × 100 + 0.001 × 100,000 = 99.9 + 100 = 199.9 ns: ≈ 2 veces más lento.
    • Disco duro: 99.9 + 0.001 × 10,000,000 = 99.9 + 10,000 = 10,099.9 ns: ≈ 101 veces.
    • Hasta 0.1 % de swap duplica el promedio. Y el promedio esconde lo peor: esa lectura de cada mil tarda mil veces más y le cae a alguna request, justo en su p99.
  9. 9Reto

    Un contenedor con límite de 8 GiB sirve con uvicorn un modelo de 2 GB en CPU. Cada worker usa ≈ 500 MB propios. (a) Si cada worker carga su copia, ¿cuántos caben? (b) ¿Y si abren el modelo con mmap? (c) Con 6 workers y mmap, ¿cuánto da la suma de sus RSS y cuánto ocupan de verdad?

    Ver la solución paso a paso
    • El límite: 8 × 1,073,741,824 B ≈ 8.59 GB.
    • (a) Cada worker ocupa 0.5 + 2 = 2.5 GB, y 8.59 ÷ 2.5 = 3.4: caben 3. Con 4 serían 10 GB: OOMKilled.
    • (b) El modelo va una sola vez: quedan 8.59 − 2 = 6.59 GB para los workers, y 6.59 ÷ 0.5 = 13.2: caben 13.
    • (c) Cada RSS marca 0.5 + 2 = 2.5 GB, y la suma de 6 da 15 GB, casi el doble del límite. De verdad ocupan 6 × 0.5 + 2 = 5 GB. Por eso se mide el total del contenedor, no la suma de las RSS.
  10. 10Reto

    C10 arranca una réplica en una máquina nueva, con 70.6 GB de pesos. (a) ¿Cuánto tarda en leerlos de un disco gp3 de base y de un NVMe a 2 GB/s? (b) Bajarlos de S3 al NVMe toma ≈ 1 a 2 minutos: ¿a qué ritmo? (c) Si la réplica se reinicia en la misma máquina y los pesos siguen en la page cache, ¿qué paso desaparece? (d) ¿Por qué C10 no mete los pesos en la imagen de Docker, como C01?

    Ver la solución paso a paso
    • (a) gp3: 70.6 GB ÷ 131 MB/s ≈ 539 s, ≈ 9 minutos. NVMe: 70.6 ÷ 2 ≈ 35 s.
    • (b) 70.6 GB en 60 a 120 s son ≈ 0.6 a 1.2 GB/s: el ritmo de bajar de S3 con muchas descargas en paralelo.
    • (c) Desaparece leer el disco: los pesos salen de la RAM. Quedan cargarlos a las GPUs y preparar el servidor, ≈ 1 a 2 minutos según la Ronda.
    • (d) Una imagen de más de 70 GB sería lentísima de bajar y de descomprimir en cada máquina, y cada versión del modelo pediría otra imagen gigante. Además, en ECR, el registro de imágenes de AWS, cada capa tiene un tope de ≈ 54.5 GB (M05). C01 sí puede, porque su engine pesa ≈ 50 a 150 MB.
12

Lo que sigue

Esencial

What comes next

En la Ronda:

  • M04 · Hardware para inferencia separa la RAM del host, la máquina donde va la GPU, de la VRAM: la primera es mucha, barata y con poco ancho de banda. También explica la memoria fijada para copiar a la GPU, la memoria unificada del Jetson y la memoria rápida dentro del chip.
  • M05 · Contenedores trae los 125 MiB/s del gp3 como cuello de botella, el NVMe local, la decisión de meter los pesos en la imagen o bajarlos al arrancar, el /dev/shm de 64 MiB y los workers que duplican la memoria.
  • M06 · Orquestación pone el límite de memoria de cada contenedor y explica el OOMKilled; M07 · Servidores de modelos cuenta las copias que hace uvicorn --workers.
  • Los casos. C01 (el engine en la imagen y el límite de 6 GiB), C07 (la page cache y el precalentamiento) y C10 (de S3 al NVMe, con mmap).

En el Dojo: la estación 7, «Empaquetado», hace la cuenta del disco antes de bajar una imagen: la de Triton pesa ≈ 10 GB comprimida y pide ≈ 30 GB mientras se descomprime. Ahí decides si el modelo va dentro de la imagen.

En Sensei: la siguiente lección, «CPU y GPU», abre los núcleos de cada chip, PCIe y la memoria de las GPUs grandes. Después, «El sistema operativo» explica los archivos, los montajes y los cgroups que ponen el límite de un contenedor, y «Hacer varias cosas a la vez» vuelve a los workers.

13

Palabras que vas a oír

Esencial

Words you will hear

Estos son los conceptos que presentó esta lección. Cada uno tiene su tarjeta con la definición completa.

RAMRAM · random access memory · main memoryLa memoria de trabajo de la computadora: donde viven los programas que están corriendo y los datos que usan. Es rápida (≈ 100 nanosegundos por lectura), pero se borra al apagar y cabe mucho menos que en un disco.Más

Cada byte de la RAM tiene una dirección, un número, y cualquiera se lee en casi el mismo tiempo, sin importar el orden: de ahí su nombre, random access memory («memoria de acceso aleatorio»). Está hecha de DRAM: cada bit es una carguita eléctrica en un capacitor diminuto que se escapa sola, así que el chip la refresca cada pocas decenas de milisegundos y sin luz la pierde. La CPU solo calcula con lo que está en la RAM (y le llega por su caché): por eso todo programa y todo archivo se copia del disco a la RAM antes de usarse. Una laptop trae de 8 a 32 GB y un servidor, de decenas de GB a varios TB, con un ancho de banda de decenas a cientos de GB/s, según la máquina (120 GB/s en una Mac M4). La GPU tiene su propia memoria aparte, la VRAM.

Analogía. La bodega de la cocina: cabe mucho más que en la mesa junto a la estufa (la caché), pero cada viaje tarda. Se rompe en que la RAM se vacía completa en cuanto se va la luz.

Ejemplo. Una foto de 12 MP, como las que toma el celular de Ana, ocupa 36.6 MB de RAM ya decodificada; el tensor que llega al servidor de GPU de C01, 602 KB. Ese servidor, una g4dn.xlarge, trae 16 GiB de RAM.

Ver en el glosario →

Caché de la CPUCPU cache · L1, L2, L3Memorias pequeñitas y muy rápidas dentro del chip de la CPU que guardan copia de lo que se usó hace poco. Si el dato está ahí (un acierto), llega en unos pocos nanosegundos; si no (un fallo), hay que ir a la RAM, la memoria principal, ≈ 100 ns.Más

Hay varios niveles (los números son de CPUs de 2022 a 2025). La L1, de 32 a 128 KiB por núcleo, responde en ≈ 1 ns; la L2, de ≈ 1 a 16 MiB, en ≈ 3 a 5 ns; la L3, de decenas de MiB o más y compartida por todos los núcleos o por un grupo de ellos, en ≈ 10 a 30 ns. El hardware la llena solo, sin que el programa lo pida, y nunca trae un byte suelto: trae una línea de 64 bytes seguidos (128 en los chips de Apple). Por eso importa la localidad: recorrer los datos en el orden en que están guardados aprovecha cada línea. Unos pocos fallos mandan sobre el promedio: 95 % de aciertos a 1 ns y 5 % de fallos a 100 ns dan ≈ 6 ns por lectura. La GPU tiene sus propias cachés junto a sus núcleos.

Analogía. La mesa junto a la estufa: lo que está ahí se usa sin esperar. Se rompe en que la mesa la acomodas tú; la caché la llena el hardware solo, y cuando pides un jitomate te sube la caja entera.

Ejemplo. Una línea de 64 bytes trae 16 números float32. Recorrer por filas una matriz de 1,024 × 1,024 float32 da 65,536 fallos; por columnas, si no cabe en la caché, 1,048,576: 16 veces más.

Ver en el glosario →

Discodisk · drive · storageDonde se guardan los archivos aunque se apague la computadora: programas, fotos, datasets y los pesos de un modelo. Cabe mucho más que en la RAM y cuesta menos por GB, pero encontrar un dato tarda de ≈ 0.1 ms (un SSD) a ≈ 10 ms (un disco duro).Más

Hay dos tipos: el disco duro (HDD, hard disk drive), con platos que giran y un brazo que se mueve, y el SSD, sin partes móviles. Un disco se lee por bloques (de 512 bytes o de 4 KiB, y el sistema operativo casi siempre pide 4 KiB o más a la vez): pedir un byte cuesta un bloque entero. Tiene tres números distintos: cuánto tarda cada lectura (su latencia), cuántas lecturas al azar aguanta por segundo (sus IOPS, input/output operations per second) y cuántos bytes por segundo da leyendo de corrido (su throughput). En la nube hay discos locales, pegados a la máquina, rápidos y que se borran al detenerla, y discos de red, como EBS gp3 en AWS, que sobreviven a la máquina pero cruzan la red en cada lectura. Lo que está en el disco se usa copiándolo antes a la RAM.

Analogía. El mercado al que vas por lo que no cabe en tu cocina: hay de todo y no se echa a perder, pero cada vuelta te toma horas. Se rompe en que, una vez que llegas, un disco te puede dar muchísimos bytes seguidos muy rápido: lo lento es empezar.

Ejemplo. Leer los 16.06 GB de Llama 3 8B en BF16 de un disco gp3 de base (125 MiB/s) tarda ≈ 2 minutos; de un NVMe local a 2 GB/s, ≈ 8 s.

Ver en el glosario →

SSDsolid-state drive · SATA · NVMeUn disco sin partes que se muevan, hecho de memoria flash (la de las memorias USB), que guarda los datos aunque se apague la computadora. Lee 4 KiB al azar en ≈ 0.05 a 0.1 ms, más de 100 veces más rápido que un disco duro. Se conecta por SATA, la conexión de los discos duros (hasta ≈ 550 MB/s), o por NVMe, una hecha para flash (varios GB/s).Más

Cada celda de flash atrapa una carga eléctrica que se queda aunque no haya luz. Se lee y se escribe por páginas de unos KiB, pero solo se borra por bloques grandes, y cada celda aguanta un número limitado de escrituras: un SSD se gasta, y usarlo de swap lo gasta más rápido. SATA es la conexión que se diseñó para discos duros: una sola fila de 32 pedidos y un tope de ≈ 550 MB/s. NVMe (Non-Volatile Memory Express) habla directo por PCIe, el camino rápido dentro de la computadora: muchas filas de pedidos (el estándar permite hasta 65,535), de ≈ 3 a 14 GB/s según la generación y cientos de miles de lecturas al azar por segundo. En AWS, varias máquinas con GPU traen un NVMe local, que se borra cuando la máquina se detiene.

Ejemplo. C10 copia los 70.6 GB de sus pesos de S3 al NVMe local del nodo en ≈ 1 a 2 minutos, porque vLLM los lee con mmap y un disco de red haría la carga mucho más lenta.

Ver en el glosario →

Jerarquía de memoriamemory hierarchyLa escalera de memorias de una computadora, de la más rápida y chica a la más lenta y grande. En la CPU: registros, cachés (L1, L2 y L3), RAM y discos. En la GPU: registros, memoria junto a los núcleos, caché L2 y VRAM. Cada piso hacia abajo guarda más y cuesta menos por byte, pero tarda más: del primero al último de la CPU, ≈ 10 millones de veces.Más

Existe porque ninguna memoria es a la vez rápida, grande y barata. El sistema trata de tener arriba lo que se va a usar enseguida, y le funciona gracias a la localidad: los programas reusan los mismos datos y usan los de al lado. A escala humana, si leer la L1 tardara 1 segundo, la RAM tardaría 1 minuto 40 segundos; un SSD, más de un día, y un disco duro, casi 4 meses. La GPU tiene su propia escalera, que termina en la VRAM (4 MB de caché L2 en la T4, decenas de MB en las más nuevas), y entre las dos escaleras el puente es PCIe, más lento que cualquiera de las dos memorias.

Analogía. Una cocina: la mesa junto a la estufa (la caché), la bodega (la RAM), el mercado (el SSD) y un pedido que llega por barco (el disco duro). Se rompe en que el hardware sube y baja las cosas solo, sin que nadie se lo pida, y en que todo lo de arriba del disco se borra si se va la luz.

Ejemplo. El mismo byte tarda ≈ 1 ns si está en la L1, ≈ 100 ns en la RAM, ≈ 0.1 ms en un SSD NVMe y ≈ 10 ms en un disco duro: diez millones de veces más del primero al último.

Ver en el glosario →

Localidadlocality of reference · temporal and spatial localityLa costumbre de los programas de volver a usar lo que acaban de usar (localidad temporal) y de usar lo que está junto (localidad espacial). Las cachés funcionan gracias a ella: por eso recorrer los datos en el orden en que están guardados es mucho más rápido.Más

La caché guarda lo reciente, para la temporal, y trae líneas completas de 64 bytes, para la espacial; además, el hardware adivina un recorrido en orden y trae las siguientes líneas antes de que se pidan (prefetch). Una matriz se guarda fila tras fila (row-major, como en NumPy y PyTorch): recorrerla por filas usa los 16 float32 de cada línea; por columnas, cada lectura cae en una línea distinta. La misma idea vale para los discos, donde leer un archivo grande de corrido le gana a leer millones de archivos chicos, y para la GPU, donde el batch reusa cada peso con muchas fotos.

Ejemplo. En el simulador de la lección, recorrer una matriz de 1,024 × 1,024 float32 por filas acierta 93.75 % de las veces; por columnas, 0 %. En Python, en la Mac donde se escribió la lección, sumar una lista en desorden tardó de 5 a 12 veces más que en orden.

Ver en el glosario →

Memoria virtualvirtual memory · pages · page tableEl truco con el que cada proceso (cada programa en marcha) ve su propia memoria, numerada desde 0, como si tuviera la máquina para él solo. El sistema operativo y la CPU traducen esas direcciones a la RAM de verdad por bloques, las páginas (4 KiB en Linux; 16 KiB en las Mac con chip de Apple).Más

Cada proceso tiene una tabla de páginas que dice dónde está cada una de sus páginas: en un marco de la RAM, en el disco (en el swap o en un archivo abierto con mmap) o en ningún lado todavía. Así un proceso no puede leer ni pisar la memoria de otro, y el sistema puede reservar sin entregar: la RAM se asigna la primera vez que se escribe en cada página. Tocar una página que no está en RAM es un fallo de página (page fault): el sistema detiene al proceso, consigue la página y lo deja seguir. Cuesta del orden de un microsegundo si basta con dar una página nueva, y de 0.1 a 10 ms si hay que leerla del disco. Lo que de verdad ocupa un proceso es su RSS, no su tamaño virtual.

Analogía. Cada proceso tiene su libreta de direcciones, con casas numeradas desde el 0, y el sistema operativo es el cartero que sabe en qué calle real está cada una. Se rompe en que este cartero también puede mandar una casa al almacén (el disco) y traerla de vuelta cuando alguien toca la puerta.

Ejemplo. Con páginas de 4 KiB, la dirección virtual 4,100 es el byte 4 de la página 1 (4,100 ÷ 4,096 da 1 y sobran 4). Si la tabla del proceso pone esa página en el marco 2, el byte de verdad está en 2 × 4,096 + 4 = 8,196. Y en la lección, pedir 400 MB no movió la RAM del proceso; escribir un byte cada 4 KiB la subió 400 MB.

Ver en el glosario →

Swapswap · swap space · paging to diskUna parte del disco donde el sistema operativo guarda los pedazos de memoria (las páginas) que no se han usado en un rato, para liberar RAM. Si un programa los vuelve a tocar, se leen del disco: de mil a cien mil veces más lento que la RAM.Más

Cuando la RAM se llena, el sistema primero suelta la page cache; si no alcanza, manda páginas poco usadas al swap o mata un proceso (el OOM). El swap evita el OOM a cambio de velocidad. Si lo que los programas usan todo el tiempo no cabe en la RAM, el sistema se pasa la vida moviendo páginas de ida y vuelta (thrashing): no se cae, pero se arrastra. En una laptop vale la pena, y macOS además comprime la memoria antes de usarlo. En un servidor que responde requests, no: la latencia explota sin ningún error, y es mejor morir rápido y volver a arrancar. Por eso los servidores suelen tenerlo apagado. En un servidor con Linux, la VRAM no tiene swap: si un tensor no cabe, PyTorch falla con «CUDA out of memory».

Ejemplo. Si 1 de cada 100 lecturas de un servidor sale del swap en un SSD (≈ 100 µs) y las demás de la RAM (≈ 100 ns), el promedio pasa de 100 ns a ≈ 1,099 ns: 11 veces más lento. Con un disco duro, ≈ 1,000 veces.

Ver en el glosario →

Page cachepage cache · file cache · buff/cacheLa copia de los archivos leídos que el sistema operativo guarda en la RAM que nadie está usando. La segunda vez que se lee un archivo, sale de la RAM en vez del disco; si un programa necesita esa memoria, el sistema la suelta.Más

En Linux, el comando free la muestra como buff/cache: por eso la memoria «libre» casi siempre se ve baja, y lo que importa es la columna available. Las escrituras también pasan primero por ahí y llegan al disco un poco después, y por eso una USB se «expulsa» antes de sacarla. Con mmap, un proceso usa directo las páginas de la page cache, sin hacer otra copia, y varios procesos comparten las mismas. En un servidor, la page cache es la diferencia entre un reinicio de segundos y uno de minutos, y medir un arranque con ella llena engaña.

Ejemplo. C07 sirve desde la page cache 51.2 GB de vectores por máquina: si no estuvieran ahí, cada máquina le pediría ≈ 175,000 lecturas al azar por segundo a un disco de red que da 40,000.

Ver en el glosario →

mmapmemory-mapped file · mmapUna forma de abrir un archivo que lo hace aparecer en la memoria del programa, como si ya estuviera cargado. El sistema lo lee del disco por pedazos, solo cuando el programa toca cada parte, y usa la copia que ya guarda en la RAM (la page cache), sin copiarlo otra vez.Más

Es lo contrario de leer el archivo completo a la memoria propia del proceso (con read), que deja dos copias en RAM: la de la page cache y la del proceso. Con mmap, abrir un archivo de 100 GB no cuesta nada hasta tocarlo, y varios procesos que abren el mismo archivo comparten la misma RAM. Los pesos en formato safetensors (un encabezado en JSON y los números tal cual) están pensados para cargarse así; torch.load(..., mmap=True) lo hace con los archivos de PyTorch, y vLLM lee los safetensors con mmap. Las páginas que se tocan cuentan en la RSS del proceso.

Ejemplo. En la lección, abrir con mmap un archivo de 200 MB y leer su encabezado y un peso del medio subió la RAM del proceso ≈ 0 MB; sumar todos los pesos la subió 200 MB.

Ver en el glosario →

RSSresident set sizeLa memoria de un proceso (un programa en marcha) que está de verdad en la RAM en este momento. Es la que importa para saber si algo cabe: lo que un proceso reserva puede ser enorme y no ocupar casi nada.Más

En ps y en top sale como RSS o RES, junto al tamaño virtual (VSZ o VIRT). Incluye las páginas que el proceso comparte con otros, como las librerías o un archivo abierto con mmap, así que sumar la RSS de varios workers cuenta esas páginas varias veces; Linux tiene otra medida, la PSS, que las reparte. El límite de memoria de un contenedor mira algo parecido a la suma de todo: la memoria propia de sus procesos más las páginas de archivos que usan activamente, cada una una sola vez. En Python, resource.getrusage(resource.RUSAGE_SELF).ru_maxrss da la RSS más alta que ha tenido el proceso: en bytes en macOS y en KiB en Linux.

Ejemplo. Cuatro workers que abren con mmap el mismo modelo de 1.2 GB marcan cada uno una RSS de ≈ 1.6 GB, y su suma da 6.4 GB; pero entre los cuatro ocupan 2.8 GB, porque el modelo está una sola vez.

Ver en el glosario →

14

Chequeo

Esencial

Check

Intenta responder en tu cabeza antes de abrir cada respuesta.

  1. 1¿Por qué una computadora tiene RAM y disco, y no una sola memoria?Ver respuesta

    Porque ninguna memoria es rápida, grande, barata y permanente a la vez. La RAM es rápida (≈ 100 ns), pero cara, y se borra al apagar; el disco guarda sin luz, pero tarda de ≈ 0.1 a 10 ms. La CPU solo calcula con lo que está en la RAM, así que todo se carga del disco antes de usarse.

  2. 2¿Qué es un fallo de caché, y por qué unos pocos cambian tanto el promedio?Ver respuesta

    Es cuando el dato no está en la caché y hay que traerlo de la RAM: ≈ 100 ns en vez de ≈ 1 ns. Con 95 % de aciertos, el promedio es ≈ 6 ns, y el 84 % del tiempo se va en ese 5 % de fallos.

  3. 3¿Por qué recorrer una matriz por filas es más rápido que por columnas?Ver respuesta

    Porque se guarda fila tras fila y la caché trae líneas de 64 bytes: por filas, cada línea sirve para 16 float32; por columnas, cada lectura cae en una línea distinta. Es la localidad espacial.

  4. 4¿Qué es la RSS de un proceso, y por qué no basta con su tamaño virtual?Ver respuesta

    Es la parte de su memoria que está en la RAM ahora. Un proceso puede reservar mucho sin ocupar nada: la RAM se entrega de página en página, cuando escribe en cada una. Ojo: la RSS también cuenta las páginas compartidas con otros procesos.

  5. 5¿Por qué el segundo arranque de un modelo es mucho más rápido que el primero?Ver respuesta

    Porque los pesos quedaron en la page cache, en la RAM libre, y el segundo arranque no toca el disco. Por eso medir en una máquina caliente engaña.

  6. 6¿Por qué los servidores de inferencia apagan el swap?Ver respuesta

    Porque el swap no tumba al servidor: lo vuelve lento sin avisar. Cada página que vuelve del swap tarda de mil a cien mil veces lo de la RAM. Es mejor que el proceso muera con un OOM y se reinicie.

Sigue · 18CPU y GPULa computadora por dentro Léelo en la RondaM04 · Hardware para inferenciaLo que acabas de leer, con el ojo del entrevistador. ← 16 · Programa, proceso e hilo