Almacenamiento en la era de la IA (2/4): rendimiento

En el primer artículo dibujamos el mapa: la jerarquía memoria-almacenamiento, las tecnologías de medio y la pila de software que domina los clústeres GPU. Este segundo artículo entra en la propiedad que justifica toda esa complejidad: el rendimiento. La pregunta que debe responder un arquitecto no es “¿cuántos GB/s da mi cabina?”, sino “¿consigo que mis GPU trabajen al 95 % en lugar de al 40 %?”. Esa diferencia es, literalmente, la mitad de la factura de cómputo.

La métrica que de verdad importa: utilización del acelerador

Es tentador medir el almacenamiento de IA con las métricas clásicas —throughput secuencial, IOPS aleatorias, latencia—, y las necesitamos. Pero ninguna de ellas captura lo que paga el negocio. La métrica integradora es la utilización del acelerador: qué fracción del tiempo la GPU está calculando en lugar de esperando datos.

La mayoría de los pipelines de ML reales corren entre el 30 % y el 60 % de utilización de GPU. Las encuestas del sector son demoledoras: solo en torno al 7 % de los equipos de IA/ML logra superar el 85 % en picos, y una mala previsión o un autoescalado deficiente pueden dejar GPUs ociosas el 70-85 % del tiempo. Un job que corre al 30 % de utilización está pagando el 70 % de su capacidad de cómputo para nada.

Podemos formalizarlo. Si ( t_{\text{cmp}} ) es el tiempo de cómputo por batch y ( t_{\text{io}} ) el tiempo que la GPU pasa bloqueada esperando I/O que no logra solaparse con el cómputo, la utilización efectiva es:

$$U = \frac{t_{\text{cmp}}}{t_{\text{cmp}} + t_{\text{io}}}$$

El objetivo de toda la ingeniería de almacenamiento para IA es llevar ( t_{\text{io}} ) hacia cero, ya sea aumentando el ancho de banda, reduciendo la latencia o solapando la I/O con el cómputo mediante prefetching y pipelining. Y hay un agravante: cuanto más rápida es la GPU, más difícil es alimentarla. El paso de V100 a H100 redujo el tiempo de cómputo por batch del modelo 3D U-Net en un 76 %, convirtiendo una carga que era sensible al ancho de banda en una sensible a la latencia. Cada generación de acelerador sube el listón para el almacenamiento.

Este cambio de régimen —de limitado por ancho de banda a limitado por latencia— tiene una consecuencia de diseño que conviene interiorizar. Cuando una carga está limitada por ancho de banda, la solución es añadir capacidad de transferencia: más NVMe, más lanes, más nodos. Cuando pasa a estar limitada por latencia, esa receta deja de funcionar; lo que importa entonces es el tiempo de respuesta por operación, que se ataca con caché, con prefetching inteligente y con rutas de datos que eliminan saltos (como GPUDirect). Diagnosticar correctamente en cuál de los dos regímenes está cada carga es la primera tarea de cualquier optimización: invertir en ancho de banda una carga limitada por latencia es gastar dinero sin mover la utilización.

MLPerf Storage: el banco de pruebas que sí mide lo correcto

Durante años faltó una forma neutral de comparar plataformas de almacenamiento para IA. MLPerf Storage, de MLCommons, la ha proporcionado. Su acierto de diseño es no medir GB/s en abstracto, sino simular el “think time” de los aceleradores para generar un patrón de I/O realista, y exigir que esos aceleradores simulados mantengan un nivel mínimo de utilización. Es decir, mide exactamente lo que importa: cuántos aceleradores puede mantener alimentados una plataforma sin que su utilización caiga por debajo del umbral.

La v1.0 (septiembre de 2024) usaba los modelos 3D U-Net, ResNet-50 y CosmoFlow, simulando A100 y H100, con umbrales de utilización del 90 % para los dos primeros y del 70 % para CosmoFlow. La v2.0 (agosto de 2025) marcó un salto: más de 200 resultados de 26 organizaciones —Alluxio, DDN, Hammerspace, HPE, IBM, KIOXIA, Micron, Oracle, Samsung, WDC, entre otras—, sistemas capaces de sostener aproximadamente el doble de aceleradores que en v1.0, y, sobre todo, una nueva carga de checkpointing (a la que volveremos).

Algunos resultados de v2.0 sirven para calibrar el estado del arte:

SistemaThroughputAceleradoresUtilización GPU
Volumez1,079 TB/s92,21 %
Hammerspace (5 nodos)420,8 GB/s140>94,7 %
Hammerspace (3 nodos)253,1 GB/s84>94,7 %
Hammerspace (1 nodo)85,6 GB/s28>94,7 %
Alluxio24,14 GiB/s12899,57 %

El dato de Hammerspace ilustra una propiedad que un arquitecto debe exigir: escalado lineal. De 1 a 5 nodos el throughput crece de 85,6 a 420,8 GB/s manteniendo la utilización por encima del 94,7 % con un coeficiente de variación ínfimo (0,08-0,14 %). El escalado lineal es lo que separa una plataforma de IA de un NAS rápido.

Las cifras de las plataformas comerciales

Fuera del banco de pruebas, las plataformas certificadas para DGX SuperPOD publican cifras agregadas que conviene tener como referencia de orden de magnitud:

  • WEKA WEKApod Nitro: 720 GB/s de lectura y 186 GB/s de escritura por configuración, 18 millones de IOPS, con NIC ConnectX-8 a 800 Gb/s. Por nodo, 70 GB/s de lectura y 40 GB/s de escritura; la configuración mínima de 8 nodos entrega 560 GB/s de lectura. Un único equipo de entrada cubre la demanda de I/O de una “scalable unit” GB200 de 1.152 GPUs.
  • DDN AI400X2: más de 90 GB/s y 3 millones de IOPS por appliance; la variante Turbo satura cerca de 100 GB/s (800 Gbps) por DGX B200. La nueva plataforma de DDN declara más de 1 TB/s de lectura por appliance con escalado por rack, y ha alimentado al supercomputador Eos de NVIDIA con 4 TB/s.
  • VAST Data: múltiples TB/s desde un único mount point vía multipathing NFS, RDMA sobre NFS y GPUDirect, gestionando exabytes en un único espacio de nombres multiprotocolo.
  • Pure Storage: certificado para DGX SuperPOD con GB200 y GB300.

Estas cifras solo significan algo en relación con el cluster que alimentan. Un GB200 NVL72 mueve 130 TB/s de comunicación interna entre GPU; el almacenamiento no compite con eso, pero sí debe sostener el data loading y el checkpointing sin estrangular a 72 GPU que se comportan como una sola.

El patrón de I/O del entrenamiento y GPUDirect Storage

Entender el patrón de I/O es la clave para no sobredimensionar ni infradimensionar. El entrenamiento combina varios patrones: lecturas grandes y secuenciales del dataset, lecturas pequeñas y aleatorias en el shuffling entre épocas, y escrituras masivas y periódicas en el checkpointing. Cada uno estresa el almacenamiento de forma distinta.

El camino tradicional de datos atraviesa la CPU: del almacenamiento a un bounce buffer en memoria del host y de ahí a la memoria de la GPU. Cada byte de cada checkpoint y de cada carga de pesos paga la latencia y el overhead de CPU de ese doble salto. GPUDirect Storage (GDS) lo corta: habilita DMA directo entre la memoria de la GPU y el NVMe mediante la librería de espacio de usuario cuFile y el módulo de kernel nvidia-fs, que intercepta las llamadas POSIX y las redirige por el motor DMA. El resultado es zero-copy, con transferencias directas que superan los 40 GB/s y, sobre todo, sin robar ciclos a la CPU.

Hay un matiz que los despliegues ingenuos pasan por alto: GDS está optimizado para transferencias grandes, alineadas y secuenciales. Para cargas dominadas por archivos pequeños o acceso muy aleatorio, el camino tradicional con caché en la CPU puede ser más eficiente. GDS no es un interruptor mágico de “más rendimiento”; es una herramienta para un patrón concreto. Diseñar el data pipeline —formato de dataset, tamaño de shard, número de workers del DataLoader— importa tanto como elegir la cabina.

Merece la pena detenerse en el data pipeline, porque es donde se pierde rendimiento de forma más silenciosa. El entrenamiento no lee el dataset de cualquier manera: lo recorre en batches, lo baraja entre épocas para evitar sesgos de orden, y a menudo aplica transformaciones (decodificación de imágenes, tokenización, augmentation) en CPU antes de entregar el tensor a la GPU. Cada uno de esos pasos puede convertirse en el cuello de botella. Un número insuficiente de workers del DataLoader deja a la GPU esperando; un formato de dataset que obliga a abrir millones de ficheros pequeños hunde el rendimiento de metadatos; un shuffle mal diseñado convierte lecturas secuenciales eficientes en accesos aleatorios costosos. La práctica habitual en cargas serias es empaquetar el dataset en shards grandes (formatos como WebDataset, TFRecord o Parquet) precisamente para que el patrón de lectura sea secuencial y grande, el terreno donde GDS y los sistemas de archivos paralelos rinden mejor. La regla mental: el almacenamiento más rápido del mundo no salva a un pipeline de datos mal diseñado.

El coste real del checkpointing (cuando hay entrenamiento)

El checkpointing es la carga de escritura más exigente del mundo de la IA, pero conviene situarla: es un problema de entrenamiento. Una factoría de inferencia pura apenas lo sufre —sus escrituras pesadas son otras: KV-cache, índices vectoriales, telemetría—. Lo tratamos aquí porque muchas instalaciones combinan algo de fine-tuning o entrenamiento ocasional con el servicio, y porque ilustra mejor que ningún otro caso cómo la asincronía vence a la fuerza bruta. Si tu plataforma es de inferencia, puedes leer esta sección como contexto y saltar a la siguiente, que es la que de verdad te concierne.

Dicho esto, la intuición de la mayoría sobre el checkpointing es errónea en dos puntos: el tamaño y la frecuencia.

El tamaño. Un checkpoint no son solo los pesos. La regla práctica es de unos 16 bytes por parámetro, porque el grueso es el estado del optimizador. Las cifras de la carga de checkpointing de MLPerf Storage v2.0, basadas en el Llama 3 Herd de Meta, lo dejan claro:

ModeloProcesosTamaño del checkpointDel cual, estado del optimizador
8B8105 GB90 GB
70B64912 GB
405B5125,29 TB
1T102415 TB13,2 TB

La frecuencia. A escala, los fallos de hardware son constantes (lo desarrollamos en el artículo sobre disponibilidad). El modelo de Meta para un cluster de 16.000 aceleradores predice un fallo cada pocas horas. Para perder menos del 5 % del progreso entre fallos hay que hacer del orden de 20 checkpoints en ese intervalo. Eso se traduce en cadencias agresivas:

  • 16.000 aceleradores: unos 155 checkpoints al día, uno cada 9,3 minutos.
  • 100.000 aceleradores: unos 967 checkpoints al día, uno cada 1,5 minutos.

Y aquí aparece el problema de ancho de banda. El checkpoint síncrono detiene el entrenamiento mientras escribe. Si ( S_{\text{ckpt}} ) es el tamaño del checkpoint y ( t_{\text{win}} ) la ventana de tiempo aceptable para escribirlo manteniendo el overhead por debajo del 5 %, el ancho de banda requerido es:

$$B_{\text{req}} = \frac{S_{\text{ckpt}}}{t_{\text{win}}}$$

Para un modelo de 1T en 100.000 aceleradores, la ventana cae a unos 4,4 segundos. Escribir 15 TB en ese tiempo exige del orden de 3,6 TB/s a través del cluster, o unos 200 GB/s sostenidos a lo largo de todo el job. Solo en checkpoints, ese escenario genera más de 14 PB de escrituras al día. No es una carga marginal: es una de las cargas de escritura más exigentes que existen.

Conviene ver el cálculo desplegado, porque revela dónde está la palanca. Tomemos el modelo de 405B, cuyo checkpoint pesa 5,29 TB. Si la política exige escribirlo manteniendo el overhead por debajo del 5 % en una cadencia donde la ventana aceptable es, digamos, de 30 segundos, el ancho de banda síncrono necesario es de unos 176 GB/s sostenidos —al alcance de una cabina de gama alta, pero solo dedicada a esa tarea—. Si en lugar de 30 segundos la ventana se contrae a 5 (clúster más grande, fallos más frecuentes), el requisito salta por encima de 1 TB/s. La sensibilidad a la ventana es brutal: la diferencia entre un diseño viable y uno imposible está en cómo se gestiona el tiempo, no en cuántos discos se compran. Ahí es donde la asincronía cambia las reglas.

La solución no es solo más ancho de banda; es asincronía. El checkpointing asíncrono desacopla el snapshot (copia GPU→CPU, rápida) de la persistencia (escritura en segundo plano, solapada con el cómputo). El Distributed Async Checkpointing de PyTorch reduce el tiempo efectivo de checkpoint entre 10 y 20 veces; en un ejemplo de IBM, un modelo de 7B bajó su down time de 148,8 a 6,3 segundos, una mejora de 23,6 veces. Técnicas como CheckFreq pipelinean el snapshot y la persistencia con el entrenamiento, permitiendo checkpointear cada 14-19 iteraciones. Por eso proveedores como VAST sostienen que la métrica relevante no es GB/s sino el checkpoint overlap: qué fracción del checkpoint se solapa con el cómputo. Un 10 % de solapamiento suele bastar.

La lección de diseño es clara: dimensionar el almacenamiento de entrenamiento por el pico de escritura del checkpointing síncrono es caro y, a menudo, innecesario si la pila de software hace bien la asincronía. Pero confiar en la asincronía sin medir el overlap es jugar a la ruleta con semanas de cómputo.

El rendimiento de una factoría de inferencia

Aquí está el corazón del asunto para quien explota una factoría de inferencia. En una factoría de inferencia el almacenamiento no se mide en épocas ni en GPU-horas, sino en la experiencia que percibe el cliente, y esa experiencia se condensa en dos métricas: el TTFT (time to first token, el tiempo hasta el primer token de respuesta) y la latencia inter-token (la velocidad a la que llegan los tokens siguientes). A escala de flota, la métrica que paga las facturas es el throughput en tokens por segundo por GPU, porque determina cuántas peticiones atiende cada acelerador. El almacenamiento toca las tres, y de formas que un diseño centrado solo en entrenamiento no anticipa.

Prefill, decode y su desagregación

La inferencia tiene dos fases con perfiles opuestos. El prefill procesa todo el prompt de entrada de una vez, genera los pares clave-valor del KV-cache y es una fase intensiva en cómputo; domina el TTFT. El decode genera los tokens uno a uno reutilizando ese caché, es intensivo en ancho de banda de memoria y domina la latencia inter-token. Como sus cuellos de botella son distintos, la tendencia de 2025-2026 es desagregarlas: ejecutar el prefill y el decode en pools de GPU separados y transferir el KV-cache entre ellos. Esa transferencia —que puede ir por NVLink, por red o a través de una capa de almacenamiento compartida— convierte el movimiento del KV-cache en una operación de rendimiento crítico. Una factoría que desagrega prefill y decode necesita una vía de KV-cache de altísimo ancho de banda y baja latencia entre ambos pools; el almacenamiento (o la DPU que lo gobierna) deja de ser un actor pasivo y entra en el camino crítico de cada petición.

El KV-cache como capa de rendimiento

El problema central es el tamaño del KV-cache con contextos largos: desborda la HBM, que es escasa y cara. Las opciones son recomputarlo en cada petición —carísimo en cómputo y letal para el TTFT— o descargarlo a una capa más lenta y reutilizarlo. Aquí compiten dos enfoques. El offload a memoria CXL ofrece mejoras de más de 5 veces frente al caching basado en SSD o RDMA, según los proveedores de la cadena CXL, y permite manejar batches hasta un 30 % más grandes manteniendo los objetivos de latencia, lo que sube el throughput y la utilización de GPU. El offload a NVMe, estandarizado por NVIDIA en 2026 con su plataforma ICMS sobre BlueField-4, es más lento pero mucho más barato y capaz, idóneo para KV-cache a escala de pod.

La gran palanca de rendimiento aquí es la reutilización de KV-cache (prefix caching). Muchas peticiones comparten prefijos: el mismo system prompt, el mismo documento de contexto, la misma conversación previa. Si el KV-cache de esos prefijos se conserva en una capa de almacenamiento rápida y se reutiliza en lugar de recomputarse, el prefill se acorta drásticamente y el TTFT se desploma. Esto convierte una capa de almacenamiento de baja latencia en un multiplicador directo del throughput de la factoría: cuanto más caché útil quepa y más rápido se sirva, menos cómputo se desperdicia recalculando lo ya calculado. Dimensionar bien esta capa —capacidad, ancho de banda, latencia, política de expulsión— es una de las decisiones de rendimiento más rentables de toda la factoría.

El tiempo de carga de modelos y el cold start

Hay un coste de almacenamiento específico de la inferencia que el entrenamiento ignora: arrancar una réplica. Cuando el autoescalado decide levantar una instancia nueva de un modelo —porque sube la demanda, porque falla una réplica o porque se rota una versión—, hay que leer los pesos del repositorio y cargarlos en la HBM. Para un modelo de decenas o cientos de GB, ese cold start puede ir de segundos a minutos según el ancho de banda del repositorio y la ruta de carga. Y durante ese tiempo, la GPU nueva no atiende peticiones mientras el sistema, quizá, está saturado. Un repositorio de modelos lento se traduce directamente en SLA incumplidos en los picos. Las mitigaciones —mantener los pesos en flash local caliente, usar formatos de carga rápida, streaming de pesos a medida que se necesitan, o GPUDirect Storage para cargar directamente a la HBM— son decisiones de almacenamiento que determinan la elasticidad real de la factoría.

El rendimiento de RAG y la búsqueda vectorial

Si la factoría sirve RAG, hay un eslabón más en el camino crítico de cada petición: la recuperación. Antes de que el modelo genere nada, el sistema busca en una base de datos vectorial los fragmentos relevantes, y esa búsqueda de similitud —sobre índices que pueden ocupar TB— se suma al TTFT. El rendimiento de la búsqueda vectorial depende de mantener los índices en flash de baja latencia y de un patrón de lectura aleatoria eficiente; un índice que no cabe en memoria y se sirve desde almacenamiento lento añade latencia a cada petición. Además, los KV-cache precomputados de los documentos recuperados aceleran el prefill, porque el modelo reutiliza claves y valores ya calculados y solo codifica la consulta del usuario. La factoría de inferencia con RAG, por tanto, tiene dos cargas de lectura sensibles a latencia conviviendo —el índice vectorial y el KV-cache— que compiten por el mismo flash rápido y que hay que dimensionar juntas.

Para un arquitecto de inferencia, todo esto introduce un plano de almacenamiento con un perfil propio: lecturas aleatorias sensibles a latencia (índices, prefijos de KV-cache reutilizables), escrituras efímeras de alta rotación (KV-cache nuevo), lecturas grandes y esporádicas pero urgentes (carga de modelos) y un flujo append-only de telemetría. No se dimensiona, en absoluto, como el almacenamiento de un clúster de entrenamiento.

Cómo medir lo propio: más allá de la hoja de especificaciones

Las cifras de los fabricantes y de MLPerf son útiles como referencia, pero el rendimiento real depende de la carga concreta, y un arquitecto debe medir, no asumir. La metodología que funciona parte de tres principios.

El primero es medir la utilización del acelerador, no el almacenamiento en abstracto. Una cabina puede entregar 500 GB/s en una prueba sintética y, sin embargo, dejar las GPU al 50 % porque el patrón real es de archivos pequeños y aleatorios que la prueba secuencial no reproduce. La pregunta correcta siempre es: ¿qué fracción del tiempo está calculando mi GPU con esta carga, este formato de dataset y este pipeline?

El segundo es reproducir el patrón de I/O real, no uno cómodo. Esto implica probar con el mismo formato de dataset, el mismo tamaño de shard, el mismo número de workers y la misma cadencia de checkpointing que se usarán en producción. El acierto de MLPerf Storage fue precisamente simular el think time del acelerador para generar un patrón realista; replicar ese rigor a escala propia es lo que separa una medición útil de un número de marketing.

El tercero es buscar el escalado, no el pico. Una plataforma que entrega un pico altísimo en una configuración pequeña pero se aplana al añadir nodos no sirve para crecer. Las pruebas deben hacerse a varias escalas para verificar que el throughput crece de forma aproximadamente lineal y que la utilización del acelerador se mantiene, como mostraba el ejemplo de Hammerspace en MLPerf v2.0. Un coeficiente de variación bajo entre configuraciones es tan importante como el pico.

El coste de equivocarse

Conviene poner número al problema. El precio de una H100 en la nube a mediados de 2026 se mueve en un rango amplio según proveedor, con una mediana de mercado en torno a 2,3-3,1 USD por hora y GPU. Multiplíquese por miles de GPU y por las semanas que dura un entrenamiento, y un 40 % de utilización en lugar de un 90 % se traduce en millones de euros de cómputo desperdiciado. Por eso más del 50 % de las organizaciones reportan en 2026 cuellos de botella de datos o almacenamiento que limitan su IA, y por eso el ancho de banda de almacenamiento ha emergido como un techo duro al escalado, comparable a la energía y la refrigeración: cuando hay data starvation, añadir cómputo da rendimientos decrecientes.

La recomendación operativa que se repite es dimensionar el pipeline de I/O para 300 Gbps o más en cargas serias, y medir —no asumir— la utilización del acelerador como indicador de salud. Un almacenamiento optimizado puede entregar hasta 5 veces el throughput de un acceso S3 estándar sobre HTTP: más de 100 GB/s agregados frente a unos 20 GB/s.

La capa física: redes y PCIe Gen6

Todo lo anterior se apoya en una capa física que también ha avanzado. La NIC ConnectX-8 de NVIDIA combina PCIe Gen6 con 800 GbE u 800 Gb de InfiniBand XDR, diseñada para los sistemas Blackwell. InfiniBand XDR ofrece 800 Gbps por dirección (el doble que NDR, que sigue siendo el storage fabric habitual), y el switch Quantum-X800 añade cómputo en red que acelera las operaciones colectivas. La transición a XDR se acelera con los despliegues Blackwell de 2025-2026. La regla mental es sencilla: el almacenamiento debe estar a la altura de la red, y la red, a la altura de la GPU. Un eslabón lento define el rendimiento de toda la cadena.

Merece la pena traducir esto a números concretos. Una NIC de 800 Gb/s entrega, en el mejor caso, unos 100 GB/s a un nodo. Si la cabina puede servir más de lo que la NIC absorbe, la red es el cuello de botella; si la cabina sirve menos, lo es el almacenamiento. El equilibrio se busca dimensionando cada capa para que ninguna estrangule a la siguiente, y verificándolo con medidas de extremo a extremo, no con las especificaciones aisladas de cada componente. Es habitual encontrar instalaciones con una cabina excelente y una fabric infradimensionada —o al revés— que rinden muy por debajo de la suma de sus partes. La capa física también impone su geografía: la topología de la red (cuántos saltos hay entre el nodo de cómputo y la cabina, si comparten conmutador o atraviesan la columna vertebral del clúster) afecta a la latencia tanto como la propia cabina, y un diseño de almacenamiento para IA que ignore la topología de red está incompleto.

Tres preguntas antes de dimensionar

Toda esta complejidad se puede ordenar en tres preguntas que un arquitecto debería responder antes de comprar nada. ¿Cuál es el patrón de I/O dominante de mi carga —secuencial grande, aleatorio pequeño, escritura en ráfagas— y, por tanto, qué métrica me limita? ¿Está mi carga limitada por ancho de banda o por latencia, y en consecuencia dónde invierto? ¿Y cuál es mi presupuesto de checkpointing —tamaño, cadencia y, sobre todo, qué fracción puedo solapar con el cómputo? Quien responde estas tres con datos medidos, y no con intuiciones, dimensiona bien; quien las salta acaba con GPU ociosas o con una cabina sobredimensionada y carísima que nunca se aprovecha. El rendimiento del almacenamiento de IA es, en el fondo, un ejercicio de adecuación: poner la capacidad correcta donde la carga la necesita, ni más ni menos.

Para llevarse a casa

El rendimiento del almacenamiento para IA no se mide en GB/s sino en utilización del acelerador, y esa utilización depende de tres cosas: ancho de banda suficiente, latencia baja y, sobre todo, solapamiento de la I/O con el cómputo. MLPerf Storage v2.0 ha dado por fin un lenguaje común para comparar plataformas con esa óptica. El checkpointing es la carga de escritura más exigente y se domina con asincronía, no solo con hardware. La inferencia de contexto largo añade una capa de almacenamiento nueva alrededor del KV-cache. Y el coste de equivocarse se mide en GPU ociosas, que es la partida más cara del presupuesto.

El rendimiento, sin embargo, no sirve de nada si los datos no están protegidos ni disponibles. El tercer artículo aborda la seguridad de esta nueva capa de almacenamiento.

Ver también

Fuentes