Un modelo de 2,78 billones de parámetros en una estación de 121 GiB

September 17, 2026 · 18 min de lectura · por Joaris Angulo

Estudio experimental. Se documenta el despliegue completo de Kimi-K3 —un modelo de 2,78 billones de parámetros— sobre una NVIDIA DGX Spark de 121 GiB de memoria unificada, ejecutando un checkpoint de 539 GiB: 4,5 veces la memoria física disponible. Todas las cifras proceden de mediciones directas sobre el equipo; no hay valores estimados ni tomados de literatura de terceros.

Joaris Angulo · Laboratorio I+D, Proyecto KIMI3 · 16 de septiembre de 2026

Resumen

Se documenta el despliegue completo de Kimi-K3 —un modelo Mixture-of-Experts de 2 779 483 135 584 parámetros— sobre una NVIDIA DGX Spark con 121 GiB de memoria unificada. El checkpoint empleado, cuantizado a IQ1_S (1,5625 bits por peso), ocupa 539 GiB: 4,5 veces la memoria física disponible. El estudio siguió el método científico sobre dos hipótesis rivales relativas al motor de inferencia, y registró dos fallos reproducibles con sus causas raíz: un intento de asignar 545 GB en memoria anclada, y un bloqueo total del sistema originado en el agotamiento del asignador de memoria del controlador NVIDIA. El sistema final alcanza 0,37 tokens/s en generación con salida correcta y terminación limpia. Se concluye que la viabilidad en esta clase de hardware no la determina la capacidad de cómputo sino la política de gestión de memoria, y que el factor de sobresuscripción impone un techo de rendimiento que ningún ajuste de parámetros logró superar.

Palabras clave

Mixture of Experts; inferencia en memoria unificada; cuantización extrema; mmap; cgroups v2; NVIDIA GB10; llama.cpp; sobresuscripción de memoria.

Esquema: 539 GiB de modelo en 121 GiB de memoria unificada
Figura 1. El problema en una imagen: un modelo de 539 GiB ejecutándose en una máquina con 121 GiB de memoria física.

1. Introducción y planteamiento del problema

1.1 Origen de la investigación

El punto de partida fue el análisis del repositorio kimi-k3-in-c de Fareed Khan, un motor de inferencia escrito en C99 puro que afirma ejecutar Kimi-K3 en equipos con apenas 8 GB de RAM mediante streaming directo desde SSD NVMe. El documento de investigación previo reportaba un binario de 176 KB, un checkpoint de 1,71 TB y una latencia de 32 a 33 segundos por token.

Ese planteamiento traslada el cuello de botella desde la memoria hacia el almacenamiento secundario, y se apoya en una propiedad arquitectónica del modelo: de sus 896 expertos, solo 16 se activan por token. El motor identifica mediante el enrutador cuáles hacen falta y lee del disco únicamente esos pesos.

1.2 Pregunta de investigación

¿Es posible ejecutar Kimi-K3 de forma útil en una DGX Spark, y qué factor limita realmente el rendimiento: el cómputo, el ancho de banda de almacenamiento, o la gestión de memoria?

1.3 Hipótesis

H1. La DGX Spark, con 121 GiB de memoria unificada frente a los 8 GB del escenario de referencia, debería mejorar sustancialmente la latencia de 32 s/token reportada en la literatura de origen.

H2. Un motor de propósito general con aceleración GPU (llama.cpp sobre GB10) superará al motor C99 monohilo orientado a CPU, al disponer de una GPU capaz de absorber las capas densas.

H3. El factor limitante será el ancho de banda de lectura del NVMe, conforme a la caracterización del proyecto original como bandwidth wall.

Como se verá en la sección 5, H1 y H2 se confirmaron; H3 resultó falsa y su refutación constituye el hallazgo principal del estudio.

2. Materiales y métodos

2.1 Plataforma experimental

Todas las especificaciones se obtuvieron por inspección directa del equipo.

Componente Especificación Fuente de verificación
Equipo NVIDIA DGX Spark (spark-6ac3) uname -a
Arquitectura aarch64 (ARM64) uname -m
Sistema operativo Ubuntu 24.04.3 LTS /etc/os-release
Núcleo 6.11.0-1016-nvidia uname -r
CPU 20 núcleos nproc
GPU NVIDIA GB10, controlador 580.95.05 nvidia-smi
Memoria 127 606 708 kB (121 GiB) unificada CPU/GPU /proc/meminfo
Almacenamiento Samsung MZALC4T0HBL1-00B07 NVMe, 3,7 TB lsblk
Compilador GCC 13.3.0 gcc --version

Tabla 1. Plataforma experimental. La memoria es unificada: la GPU no dispone de VRAM dedicada, sino que asigna del mismo banco LPDDR5X que usa el anfitrión. Este detalle resulta determinante en la sección 4.2.

2.2 Selección del checkpoint

Se evaluaron cuatro repositorios mediante consultas a la API de Hugging Face, midiendo el tamaño real por suma de bytes de los archivos y comprobando el estado de restricción de acceso.

Repositorio Formato Tamaño Acceso Decisión
Ryanchen911/Kimi-K3-Uncensored-GGUF GGUF IQ1_S-XS 539 GiB Abierto SELECCIONADO
Uniboshi/Kimi-K3-Abliterated-V1 safetensors 615 GiB Restringido (auto) Descartado: requiere credencial
audnai/penclaw-Kimi-K3.0-abliterated GGUF UD-Q2_K_XL — Restringido (manual) Descartado: aprobación humana
moonshotai/Kimi-K3 safetensors 1,42 TiB Abierto Descartado: no cabe en disco

Tabla 2. Matriz de selección. El criterio decisivo fue la disponibilidad inmediata combinada con la compatibilidad verificada del motor.

La compatibilidad se verificó en el código fuente antes de comprometer la descarga: llama.cpp declara el identificador de arquitectura en src/llama-arch.cpp:151.

{ LLM_ARCH_KIMI_K3,          "kimi-k3"          },

2.3 Diseño experimental

El estudio se organizó en cinco fases secuenciales, cada una con un criterio de aceptación explícito:

  • Fase I — Validación del motor C99 sin pesos, contra un oráculo de referencia.
  • Fase II — Adquisición y verificación byte a byte del checkpoint.
  • Fase III — Puesta en marcha del servicio de inferencia.
  • Fase IV — Caracterización de fallos y análisis de causa raíz.
  • Fase V — Barrido del presupuesto de memoria y medición de rendimiento.

3. Resultados: validación y adquisición

3.1 Fase I — Validación del motor C99

El motor kimi-k3-in-c compiló sin errores en aarch64 produciendo un binario de 211 936 bytes con -O3 -std=gnu99 -mcpu=native -fopenmp. La suite de pruebas, que no requiere pesos del modelo, se ejecutó contra un oráculo de referencia en Python.

GATE 1  teacher forcing : 32/32 positions match tf_pred
        generated span  : 20/20  <- must be exact
GATE 1b state reuse      : PASS  <- all logits bit-identical
GATE 2  greedy decode   : 20/20 generated tokens match full_ids
GATE 3  incremental    : 20/20 generated tokens match full_ids
 
VERDICT: ENGINE MATCHES THE REFERENCE EXACTLY

El motor es por tanto numéricamente correcto en esta plataforma. Sin embargo, la inspección de su lector de safetensors reveló un impedimento decisivo.

El archivo src/io/k3_st.c admite exclusivamente los tipos U8, BF16, F16 y F32. No lee GGUF. Dado que el único checkpoint accesible sin credencial estaba en GGUF, este motor quedó inutilizable para el despliegue, pese a haber superado toda la validación. Se conservó como control de correctitud.

Observación metodológica: la validación de la Fase I no fue trabajo perdido. Estableció que el hardware y el compilador producen aritmética correcta, lo que permitió descartar la plataforma como fuente de error en fases posteriores.

3.2 Fase II — Adquisición del checkpoint

Métrica Valor medido
Fragmentos descargados 34 de 34
Tamaño final verificado 579 511 906 185 bytes
Duración total 2 h 45 min 04 s
Rendimiento medio efectivo ≈ 56 MiB/s
Rango instantáneo observado 11 – 134 MiB/s
Errores de transporte 2 (IncompleteMessage), reintentados sin pérdida
Código de salida rc=0

Tabla 3. Adquisición del checkpoint. La alta variabilidad del rendimiento instantáneo corresponde a limitación del proveedor remoto, no del enlace local.

4. Resultados: caracterización de fallos

Se registraron dos fallos reproducibles. Ambos comparten una raíz común —la naturaleza unificada de la memoria en GB10— pero se manifiestan por mecanismos distintos.

4.1 Fallo I — Asignación en memoria anclada

El primer intento de arranque abortó de inmediato:

E ggml_aligned_malloc: insufficient memory (attempted to allocate 520563.75 MB)
E ggml_backend_cpu_buffer_type_alloc_buffer: failed to allocate buffer of size 545850654720
E alloc_tensor_range: failed to allocate CUDA_Host buffer of size 545850654720
E llama_model_load: error loading model: unable to allocate CUDA_Host buffer

Causa raíz

La opción --cpu-moe ubica los pesos de los expertos en un búfer CUDA_Host (memoria anclada, pinned). Ese tipo de búfer no admite respaldo por mmap: exige residencia física. El motor solicitó por tanto 545 GB de RAM real contra 121 GiB disponibles.

Corrección

La bandera --no-host ("bypass host buffer allowing extra buffers to be used") desvía los pesos a un búfer CPU convencional, que sí se respalda con el mapeo. Combinada con --load-mode mmap, el modelo cargó correctamente.

4.2 Fallo II — Bloqueo total del sistema

Durante una generación prolongada el equipo dejó de responder por completo, requiriendo reinicio en frío. El registro del núcleo del arranque anterior identificó el origen:

NVRM: nvAssertOkFailedNoLog: Assertion failed: Out of memory [NV_ERR_NO_MEMORY]
      (0x00000051) returned from status @ kernel_graphics_context.c:1178
NVRM: nvCheckOkFailedNoLog: Check failed: Out of memory [NV_ERR_NO_MEMORY]
      returned from kgrctxAllocMainCtxBuffer(pGpu, pKernelGraphicsContext,
      pKernelGraphics, pKernelChannel) @ kernel_graphics_context.c:1387

Causa raíz

El fallo no fue saturación de disco ni del planificador, como sugeriría la hipótesis H3. En GB10 la GPU carece de VRAM dedicada y asigna del mismo banco de 121 GiB que el anfitrión. Concurrieron tres consumidores:

  • La memoria residente del proceso de inferencia.
  • La caché de páginas generada por el mapeo de 539 GiB, que crece hasta ocupar toda la memoria libre.
  • El servicio ollama, que retenía 36 272 MiB de memoria de GPU de forma simultánea.

El controlador NVIDIA no pudo reservar los búferes de contexto gráfico —asignaciones no reclamables— y el sistema quedó inoperante.

Corrección aplicada

Medida Implementación Efecto
Aislamiento de memoria MemoryMax en cgroup v2 Confina el reclamo al servicio en lugar de afectar al sistema
Eliminación del competidor systemctl disable ollama Libera 36 GiB de memoria unificada
Verificación previa Guardia en el script de arranque Rechaza el arranque si la GPU está ocupada
Supresión del intercambio MemorySwapMax=0 Evita degradación adicional por paginación
Política ante agotamiento OOMPolicy=stop, Restart=no Impide que un fallo se convierta en bucle

Tabla 4. Medidas correctivas. El guardia de arranque se validó experimentalmente: rechazó un arranque con 36 442 MiB de GPU ocupados.

5. Resultados: rendimiento

5.1 Barrido del presupuesto de memoria

Prefill y generación con 60, 68 y 92 GiB de presupuesto
Figura 2. Rendimiento medido con tres presupuestos de memoria. Con 60 GiB la carga no llegó a converger en quince minutos.

Se midió el rendimiento bajo tres presupuestos de memoria distintos, manteniendo constante el resto de la configuración salvo donde se indica.

Presupuesto Sobresuscripción Carga Prefill (tok/s) Generación (tok/s)
68 GiB 7,9 × 11 min 33 s 0,286 0,322
60 GiB 9,0 × no convergió en 15 min 0,18 0,23
92 GiB 5,9 × 15 min 21 s 0,30 0,37

Tabla 5. Rendimiento frente al presupuesto de memoria. Advertencia metodológica: la fila de 60 GiB incorpora además cambios en el tamaño de lote y en el presupuesto de razonamiento, por lo que su comparación no es estrictamente controlada. Las filas de 68 y 92 GiB sí son comparables entre sí.

5.2 Evidencia de presión de memoria

Con el presupuesto de 60 GiB, la interfaz memory.pressure del cgroup registró:

some avg10=53.00 avg60=47.61 avg300=21.55
full avg10=53.00 avg60=47.61 avg300=21.55

El valor full avg10 = 53,00 indica que durante el 53 % del tiempo la totalidad de los procesos del grupo estuvieron detenidos esperando reclamo de memoria. Más de la mitad del tiempo de reloj se consumía en gestión de páginas, no en cómputo útil. La memoria consumida permanecía además fijada exactamente en el límite configurado, confirmando saturación sostenida.

5.3 Validación funcional final

La configuración definitiva se sometió a una inferencia completa de extremo a extremo:

CONTENT: '¡Hola! ¿En qué puedo ayudarte hoy? 😊'
FINISH: stop
gen 0.37 tok/s | prefill 0.30 tok/s | n=152

El indicador finish_reason: stop confirma que el modelo terminó por decisión propia y no por agotar el presupuesto de tokens. La salida es semánticamente correcta.

5.4 Hallazgo secundario: modelo de razonamiento

Kimi-K3 emite su cadena de pensamiento en un campo separado, reasoning_content, antes de producir la respuesta. Con presupuestos bajos de tokens el campo content regresa vacío:

"content": ""
"reasoning_content": "The user says "Di hola" which is Spanish for "Say hello."..."
"finish_reason": "length"

Este comportamiento es fácilmente confundible con un fallo del servicio. La mitigación consiste en fijar max_tokens por encima de 600, o limitar explícitamente el razonamiento mediante --reasoning-budget.

6. Discusión

6.1 Contraste de hipótesis

32 s/token del motor C99 frente a 2,7 s/token con llama.cpp
Figura 3. El cambio de motor, no el hardware, explica la mejora: de 32 a 2,7 segundos por token.
Hipótesis Resultado Evidencia
H1 — Mejora sobre 32 s/token CONFIRMADA 2,7 s/token medidos, frente a 32–33 s/token de referencia: mejora de ~12 ×
H2 — llama.cpp supera al motor C99 CONFIRMADA Confirmada, aunque por vía forzosa: el motor C99 no admite GGUF y no pudo compararse directamente
H3 — El límite es el ancho de banda NVMe REFUTADA El límite es la política de memoria. Dos fallos se originaron en asignación de memoria; ninguno en saturación de disco

Tabla 6. Contraste de hipótesis.

6.2 Interpretación del hallazgo principal

La refutación de H3 es el resultado de mayor valor. El proyecto original caracteriza el problema como un bandwidth wall, y esa descripción es válida para una arquitectura con memoria de sistema y VRAM separadas. En una arquitectura de memoria unificada como GB10 la restricción cambia de naturaleza: la caché de páginas y el controlador gráfico compiten por el mismo recurso físico, y esa competencia —no la velocidad del disco— es la que determina tanto la estabilidad como el rendimiento.

La consecuencia práctica es que la variable de diseño relevante en esta clase de hardware no es la velocidad de almacenamiento sino el aislamiento de memoria entre consumidores. Un sistema con un NVMe el doble de rápido habría sufrido exactamente el mismo bloqueo.

6.3 El techo de sobresuscripción

El rendimiento mejoró de forma consistente al ampliar el presupuesto de memoria —un 61 % en generación al pasar de 60 a 92 GiB— pero permaneció en el orden de 0,3 tokens/s en todas las configuraciones. El conjunto de trabajo de 539 GiB excede cualquier presupuesto alcanzable en esta plataforma por un factor mínimo de 4,5. Cada token exige fallos de página que obligan a reclamo, y ningún ajuste de parámetros elimina ese coste estructural.

6.4 Limitaciones del estudio

  • Cada punto de rendimiento procede de una sola ejecución. No se calcularon medias ni desviaciones, por lo que las cifras indican orden de magnitud y dirección, no valores de precisión estadística.
  • La condición de 60 GiB varía simultáneamente tres parámetros, lo que impide atribuir su degradación exclusivamente al presupuesto de memoria.
  • No se evaluó la calidad de las respuestas. La cuantización a 1,5625 bpw es extrema y su impacto sobre el razonamiento del modelo queda fuera del alcance de este trabajo.
  • El motor C99 no pudo compararse de forma directa por incompatibilidad de formato, de modo que H2 se confirma por eliminación y no por medición contrastada.

7. Conclusiones

1. Es viable ejecutar un modelo de 2,78 billones de parámetros en una estación de 121 GiB. El sistema está operativo, sirve una API compatible con OpenAI y produce salidas correctas con terminación limpia.

2. No es viable para uso interactivo. A 0,37 tokens/s, una respuesta de 150 tokens requiere cerca de siete minutos. El sistema es apto para procesamiento por lotes y experimentación, no para diálogo.

3. El factor limitante en memoria unificada es la política de gestión de memoria, no el ancho de banda de almacenamiento. Este es el hallazgo central y contradice la caracterización previa del problema.

4. El aislamiento mediante cgroups es un requisito de seguridad operativa, no una optimización. Sin él, un proceso de inferencia puede inutilizar la estación completa al privar de memoria al controlador gráfico.

5. La sobresuscripción impone un techo infranqueable. Ampliar el presupuesto de memoria mejora el rendimiento de forma monótona, pero mientras el conjunto de trabajo multiplique por 4,5 la memoria física el orden de magnitud no cambia.

7.1 Dimensionamiento de una granja para 50 usuarios

Equipos, potencia e inversión para tres escenarios de uso
Figura 4. Lo que costaría atender a 50 personas con este modelo, en tres escenarios de intensidad de uso.

Se extrapola el rendimiento medido a una operación empresarial de 50 usuarios. El cálculo parte exclusivamente de la cifra experimental de 0,37 tokens/s por equipo y ranura, y explicita todas las premisas.

Premisas del modelo

  • Rendimiento unitario: 0,37 tokens/s por DGX Spark, con una única ranura de inferencia (valor medido, sección 5.3).
  • Jornada efectiva de 8 horas. La carga de una empresa se concentra en horario laboral, no se reparte en 24 horas.
  • Utilización objetivo del 70 %. Al 100 % la longitud de cola diverge; el 30 % de holgura absorbe la variabilidad de llegadas.
  • Cada petición ocupa un equipo en exclusiva, al ser –parallel 1 la configuración estable verificada.
  • La longitud de respuesta incluye la cadena de razonamiento, que en este modelo consume la mayor parte del presupuesto de tokens.

Resultado del dimensionamiento

Escenario Peticiones usuario/día Tokens por respuesta Latencia unitaria Equipos necesarios
Ligero 5 300 13,5 min 11
Moderado 20 500 22,5 min 68
Intensivo 50 800 36,0 min 269

Tabla 8. Equipos necesarios para 50 usuarios según intensidad de uso, al 70 % de utilización sobre jornada de 8 horas. El escenario moderado representa el caso de referencia para uso profesional.

Implicaciones de infraestructura

Escenario Equipos Potencia eléctrica Inversión aproximada Almacenamiento total
Ligero 11 2,6 kW 44 000 USD 5,8 TiB
Moderado 68 16,3 kW 272 000 USD 35,8 TiB
Intensivo 269 64,6 kW 1 076 000 USD 141,6 TiB

Tabla 9. Consumo, inversión y almacenamiento derivados. Potencia estimada en 240 W por equipo e inversión sobre un precio de lista de referencia de 4 000 USD; ambas cifras deben confirmarse con el proveedor. Cada equipo requiere su propia copia del checkpoint de 539 GiB.

Objeciones al enfoque de granja

La latencia no mejora al añadir equipos. Una granja escala el caudal agregado, no el tiempo de una respuesta individual. Incluso con 269 equipos, un usuario sigue esperando 36 minutos por su respuesta. Ninguna operación empresarial tolera esa latencia en tareas interactivas.

El cuello de botella se replica, no se resuelve. Cada equipo de la granja arrastra la misma sobresuscripción de 4,5 × documentada en la sección 6.3. Se está multiplicando una ineficiencia en lugar de corregirla.

La agregación de memoria sería la vía correcta, pero no está disponible. Bastarían 5 equipos para que los 539 GiB residieran íntegramente en memoria agregada, eliminando la causa raíz. Sin embargo, la interconexión de DGX Spark admite oficialmente agrupaciones de dos nodos, insuficiente para ese objetivo. Verificar esta limitación con el fabricante es condición previa a cualquier decisión de compra.

Recomendación

No se recomienda una granja de DGX Spark para servir Kimi-K3 en IQ1_S a 50 usuarios. El escenario moderado exige 68 equipos y 272 000 USD para entregar respuestas de 22 minutos, resultado inferior al de alternativas de menor coste.

Tres vías alternativas, por orden de relación coste-beneficio:

  • Emplear un modelo que quepa en la memoria de un solo equipo. Eliminada la sobresuscripción, un único Spark podría atender varios usuarios concurrentes, sustituyendo decenas de equipos.
  • Concentrar el cómputo en GPU de centro de datos con memoria suficiente para residencia completa, en lugar de distribuirlo entre nodos de memoria insuficiente.
  • Reservar esta configuración para cargas por lotes sin requisito de latencia —análisis nocturnos, procesamiento documental— donde 22 minutos por respuesta es aceptable y bastan uno o dos equipos.

7.2 Trabajo futuro

  • Evaluar kimi-k3-in-c con un checkpoint en safetensors. Su presupuesto de memoria es un parámetro explícito y emplea O_DIRECT, evitando por diseño la presión sobre la caché de páginas que originó el Fallo II.
  • Repetir el barrido con tres o más repeticiones por punto para establecer intervalos de confianza.
  • Contrastar la calidad de las respuestas frente a cuantizaciones menos agresivas, para determinar si IQ1_S preserva la capacidad de razonamiento del modelo base.
  • Caracterizar el patrón de acceso a expertos y evaluar si una política de precarga guiada por el enrutador reduce los fallos de página.

8. Anexo: configuración final

Parámetros de arranque del servicio en producción:

llama-server 
    --model Kimi-K3-abliterated-IQ1_S-XS-00001-of-00034.gguf 
    --alias kimi-k3-unc 
    --host 0.0.0.0 --port 7777 
    --ctx-size 4096 
    --parallel 1 
    -b 2048 -ub 2048 
    --cache-reuse 256 
    --reasoning-budget 2048 
    --n-gpu-layers 999 
    --cpu-moe 
    --no-host 
    --load-mode mmap 
    --threads 20 
    --no-warmup

8.1 Parámetros críticos

Parámetro Justificación experimental
--no-host Obligatorio. Sin él, el arranque aborta al intentar asignar 545 GB anclados (Fallo I)
--load-mode mmap Único modo viable. DirectIO exigiría residencia completa del modelo
--parallel 1 Reducido desde 4. Cada ranura mantiene su propia caché KV
MemoryMax=92G Aislamiento en cgroup. Deja ~29 GiB al controlador NVIDIA y al sistema
--reasoning-budget 2048 Acota la cadena de pensamiento para que content no regrese vacío

Tabla 7. Parámetros críticos y su fundamento empírico.

8.2 Hallazgo operativo: clientes en PowerShell

En Windows PowerShell el identificador curl es un alias de Invoke-WebRequest, que altera el escapado del cuerpo JSON. Se verificó experimentalmente que el envío en línea produce:

{"error":{"code":500,"message":"[json.exception.parse_error.101] parse error
 at line 1, column 2: syntax error while parsing object key - invalid literal;
 last read: '{c'","type":"server_error"}}

La solución validada consiste en invocar curl.exe de forma explícita y pasar el cuerpo desde archivo con -d "@archivo.json". El operador de detención de análisis --% se probó y no resuelve el problema.

Referencias

[1] Khan, F. kimi-k3-in-c: motor de inferencia en C99 para Kimi-K3. Repositorio GitHub, FareedKhan-dev/kimi-k3-in-c.

[2] Moonshot AI. Kimi-K3. Repositorio Hugging Face, moonshotai/Kimi-K3.

[3] Ryanchen911. Kimi-K3-Uncensored-GGUF. Repositorio Hugging Face.

[4] Gerganov, G. et al. llama.cpp, compilación b460-6ca4915, 27 de agosto de 2026.

[5] Documentación del núcleo Linux. Control Group v2: interfaz de presión de memoria (PSI).

Deja un comentario

Your email address will not be published. Required fields are marked *

¿Empezamos por un Discovery Call?

30 minutos, sin coste ni compromiso. Salimos de la llamada con el proceso candidato identificado y un ROI estimado.

+57 314 627 9741 contacto@autoonomia.com Respuesta en menos de 24 h hábiles