Espresso es una biblioteca de código abierto en Swift que ejecuta transformers en el Neural Engine (ANE) de los Mac con Apple Silicon sin usar Core ML. Genera el programa de cada capa en MIL, el lenguaje intermedio de Apple, y lo compila con dos clases privadas de macOS que su autor, Christopher Karani, ha documentado por ingeniería inversa. Aquí tienes qué hace distinto, cómo funciona, qué miden sus cifras frente a Core ML, qué pasa con un modelo real como Qwen2.5 y qué riesgos tiene usar APIs privadas. No lo hemos ejecutado en un Mac propio: todo sale del repositorio y de su historial, revisados el 25 de octubre de 2026. Hay una versión en inglés de este artículo.

Puntos clave

  • Espresso compila programas MIL para el ANE con las clases privadas _ANEClient y _ANEInMemoryModel, una vez por modelo, y guarda la caché KV en buffers IOSurface compartidos con la CPU.
  • En un modelo de prueba de 6 capas en un M3 Max, la ruta fusionada de Espresso tarda 1,93 ms por token (519 tokens/s) y Core ML tarda 6,58 ms (152 tokens/s): 3,41 veces más.
  • Con Qwen2.5-0.5B, 10 de 12 secuencias y 341 de 384 tokens coinciden con la referencia de PyTorch, pero la atención, RoPE y la capa de salida corren en la CPU, y el proyecto no da cifras de velocidad.
  • La capa de salida de Qwen2.5-0.5B ocupa 272 MB y la memoria SRAM disponible en el ANE ronda los 32 MB, así que no cabe.
  • Una app que use APIs privadas de Apple no entra en la App Store, y una actualización de macOS puede romperla.

Qué es Espresso y qué hace distinto

Espresso es un paquete de Swift 6.2 con licencia MIT que habla con el Neural Engine sin Core ML de por medio. El README de Espresso[1] lo resume así: "No CoreML in the hot path. No per-token recompilation." El proyecto nació el 4 de marzo de 2026, tiene 392 commits, 173 estrellas y una versión publicada, la v0.9.0, del 12 de agosto.

El Neural Engine es el acelerador de redes neuronales que Apple integra en sus chips junto a la CPU y la GPU. Apple lo presenta como "the energy-efficient and high-throughput engine for ML inference on Apple silicon". Su guía para desplegar transformers en el Neural Engine[2] da su pico en FP16: 0,6 TFLOPS en el A11 de 2017 y 15,8 TFLOPS en el ANE de 16 núcleos de 2021. La única vía pública para usarlo es Core ML, y Core ML decide en tiempo de ejecución qué operaciones van al ANE y cuáles a la CPU.

Las herramientas habituales para ejecutar LLM en un Mac, como oMLX u Ollama con MLX, usan la GPU con Metal. Espresso apunta al ANE, que consume menos energía, y lo hace sin el envoltorio de Core ML. La biblioteca no depende de paquetes de terceros: solo de Foundation, Accelerate, IOSurface, Metal y Core ML.

Cómo funciona Espresso por dentro

Espresso sustituye la compilación de Core ML por la suya propia y reutiliza el programa compilado en cada token. Según el README, la secuencia es esta:

  1. El módulo MILGenerator escribe en texto el programa MIL de cada kernel, con variantes que fusionan hasta tres capas en un solo envío al ANE
  2. ANEInterop carga con dlopen las clases privadas _ANEClient y _ANEInMemoryModel y compila ese texto a un binario del ANE, que el sistema guarda en caché
  3. Las entradas, los pesos y las salidas viven en buffers IOSurface, memoria que comparten la CPU y el ANE, así que leer un resultado no exige copiarlo
  4. El bucle de generación compila una vez y repite la evaluación en cada paso, con la caché KV también en IOSurface

Otros proyectos han seguido el mismo camino. El artículo Orion, publicado en arXiv el 6 de marzo de 2026[3], compila MIL y lo evalúa con _ANEClient. Cataloga 20 restricciones del ANE (14 sin documentar hasta entonces) y genera más de 170 tokens/s con GPT-2 124M en un M4 Max.

El repositorio ane de mdaiter[4] documenta que, sin los permisos adecuados, _ANEClient se crea pero compileModel: devuelve NULL sin error. Espresso usa la ruta de modelo en memoria y declara funcionar en macOS 15 sin permisos especiales; en iOS sí los necesita.

Cuánto más rápido es Espresso que Core ML

Espresso genera 3,41 veces más tokens por segundo que Core ML en la única prueba que publica, y esa prueba usa un modelo de juguete. El fichero latest.json de los benchmarks[5] es del 17 de marzo de 2026. Mide un modelo de 6 capas tipo Llama entrenado con TinyStories (dimensión 768, 12 cabezas, vocabulario de 32 000 tokens, secuencia de 256) en un M3 Max con macOS 15.0:

Generación de tokens en el Neural Engine de un M3 Max, modelo de 6 capas tokens/s · más es mejor
Espresso, decodificación recurrente fusionada
519
Espresso, transformer sin fusionar
153
Core ML, .cpuAndNeuralEngine
152
Ver tabla de valores
Ruta de ejecucióntokens/s
Espresso, decodificación recurrente fusionada519
Espresso, transformer sin fusionar153
Core ML, .cpuAndNeuralEngine152

Cifras de benchmarks/results/latest.json del repositorio de Espresso (17 de marzo de 2026), modelo stories110m de 6 capas, macOS 15.0. No medido por jacar.

El gráfico deja ver de dónde sale la ganancia. Sin fusionar capas, Espresso rinde lo mismo que Core ML (153 frente a 152 tokens/s). La ventaja aparece con la ruta que fusiona tres capas en cada envío al ANE y calcula también en el ANE la capa de clasificación final.

El propio README acota sus cifras: no son una comparación con GPT-2 de 117 millones de parámetros ni con llama.cpp sobre Metal, y el modelo es "a research / demo artifact family, not a pretrained production model". Un detalle que da confianza es que la integración continua del proyecto falla si la tabla del README deja de coincidir con latest.json.

Qué pasa con un modelo real como Qwen2.5

Con modelos de Hugging Face, Espresso reparte el trabajo entre el ANE y la CPU, y su documentación evita dar cifras de velocidad. El informe de paridad de Qwen2.5-0.5B[6] detalla qué corre dónde:

Etapa Dónde corre Motivo
Proyecciones Q, K y V ANE Un kernel por capa
Red feed-forward SwiGLU ANE Kernels fusionados
RoPE, softmax de atención y caché KV CPU Trabajo secuencial ligado a la caché
Capa de salida (151 936 × 896) CPU 272 MB frente a unos 32 MB de SRAM en el ANE

Con esa división, 10 de 12 secuencias de prueba y 341 de 384 tokens coinciden con PyTorch en FP32 con decodificación voraz. Las dos secuencias que se desvían lo hacen por la precisión FP16 del ANE, con errores de hasta un logit, y en los dos casos eligen el segundo token de la referencia. El documento lo cierra así: "That is one model, greedy, fp16, measured — not a general-model claim, and not a speed claim."

El informe de Qwen2.5-1.5B[7] añade las limitaciones del hardware que se encontró el autor:

  • Presupuesto de compilación: cada proceso solo puede compilar un número finito de programas para el ANE, y 720 compilaciones lo agotarían
  • Anchos de capa: Qwen3-0.6B acumuló 156 reintentos y 195 fallos de compilación; Qwen2.5-0.5B, con todas sus dimensiones múltiplos de 64, compila sus 24 capas sin fallos en unos 20 s
  • Desbordamiento en FP16: en el modelo de 1,5B, la media de los cuadrados de RMSNorm desborda FP16 cuando el RMS residual pasa de 256, y Espresso escala el residuo por 1/4 antes de elevarlo
  • Capa de salida: en el modelo de 1,5B ocupa unos 467 MB y también se queda en la CPU

Cómo probar Espresso en tu Mac

La demo se instala con tres órdenes y necesita un Mac con Apple Silicon, macOS 15 o superior y Swift 6.0 o superior. Estos son los pasos del README a 25 de octubre de 2026:

git clone https://github.com/christopherkarani/Espresso.git
cd Espresso
./espresso doctor
./espresso prepare
./espresso

doctor comprueba que el equipo tiene ANE, los scripts y un Python utilizable. prepare descarga los pesos de demostración de GPT-2 y el tokenizador, para lo que necesita red y un Python con torch y transformers (Espresso puede crear su propio entorno virtual). La última orden compila el proyecto si hace falta y abre la demo de texto en la terminal.

Para conversar con un modelo real, el README convierte Qwen2.5-1.5B-Instruct al formato .esp del proyecto y abre un chat:

python3 scripts/convert_qwen25_05b_to_esp.py \
  --model Qwen/Qwen2.5-1.5B-Instruct
./espresso chat --model \
  ~/Library/Caches/Espresso/qwen25-15b/Qwen2.5-1.5B-Instruct.esp

El script de conversión descarga el modelo de Hugging Face si no lo tienes y empaqueta los pesos. El chat muestra en el pie los tokens por segundo, el tiempo hasta el primer token y los julios por token de cada respuesta. Con --vs mlx --greedy abre un segundo panel con MLX y el mismo modelo, que es la comparación que te interesa hacer en tu propia máquina.

Riesgos de usar las APIs privadas del Neural Engine

El riesgo principal de Espresso es el mismo que le da su ventaja: depende de clases que Apple no documenta ni garantiza. El README lo dice sin rodeos: "Apps using private ANE APIs (_ANEClient, _ANEInMemoryModel) will be rejected." Estas son las consecuencias prácticas:

  • App Store: una app que enlace estas clases será rechazada en la revisión de Apple
  • Actualizaciones de macOS: Apple puede cambiar o retirar las clases privadas en cualquier versión sin aviso
  • iOS y tvOS: no funcionan tal cual, porque los permisos de las APIs privadas cambian por plataforma
  • Precisión: el ANE calcula en FP16, y en modelos grandes los errores se acumulan capa a capa
  • Madurez: la última versión publicada es la v0.9.0, pero la tabla de benchmarks cita una "Espresso 1.1.0" que no existe como versión

Hay otro detalle que conviene corregir si lees el README. Su tabla de compatibilidad atribuye al M3 un "18-core ANE" y al M4 uno de "38-core". Son cifras de rendimiento en billones de operaciones por segundo (TOPS) que Apple publicó para esos chips, no núcleos, y el propio latest.json del proyecto describe el ANE del M3 Max como de 16 núcleos.

Espresso frente a Core ML, MLX y llama.cpp

Espresso no compite hoy con las herramientas de GPU para ejecutar LLM grandes; es un proyecto de investigación sobre el ANE. Esta tabla resume las diferencias:

Criterio Espresso Core ML MLX y llama.cpp
Unidad de cálculo ANE, con atención en CPU La que elija Core ML GPU con Metal
API Privada de Apple Pública Pública
Apta para la App Store No Sí Sí
Modelos probados Demo de 6 capas, Qwen2.5 0,5B y 1,5B Cualquiera convertido a Core ML Miles en Hugging Face
Precisión FP16 FP16 en el ANE Cuantizada, FP16 o BF16

Si quieres ejecutar un modelo de 8B o más en tu Mac, sigue con MLX o con modelos cuantizados en llama.cpp. Espresso tiene sentido si investigas el ANE, si te interesa el consumo por token de un modelo pequeño o si quieres ver cómo se programa un acelerador que Apple mantiene cerrado.

Preguntas frecuentes

¿Espresso funciona en un Mac con Intel?

No. Los Mac con Intel no tienen Neural Engine, y Espresso necesita Apple Silicon (M1 o posterior) con macOS 15 o superior.

¿Puedo usar Espresso en una app de la App Store?

No. El propio README avisa de que Apple rechaza las apps que usan _ANEClient o _ANEInMemoryModel. Para una app publicada, la vía es Core ML.

¿Es más rápido que MLX para chatear con un LLM?

El proyecto no lo afirma. Su única cifra de velocidad compara con Core ML en un modelo de 6 capas, y para Qwen2.5 solo publica paridad. El chat incluye un modo --vs mlx para que lo midas tú.

Conclusión

Espresso demuestra que el Neural Engine del Mac puede ejecutar un transformer desde Swift sin Core ML, y que fusionar capas lo hace 3,41 veces más rápido en un modelo pequeño. Con modelos reales el ANE solo se queda las proyecciones y la red feed-forward, y la memoria SRAM de unos 32 MB deja la capa de salida en la CPU. Si tienes un Mac con Apple Silicon, ejecuta la demo y el chat con --vs mlx para medirlo en tu equipo, pero no lo metas en una app que vaya a la App Store.

Fuentes: [1] Christopher Karani, repositorio Espresso[1], [2] Espresso, benchmarks/results/latest.json[5], [3] Espresso, informe de paridad de Qwen2.5-0.5B[6], [4] Espresso, informe de paridad de Qwen2.5-1.5B[7], [5] Apple Machine Learning Research, Deploying Transformers on the Apple Neural Engine[2], [6] Ramchand Kumaresan, Orion en arXiv[3], [7] mdaiter, repositorio ane[4].

Fuentes

  1. README de Espresso
  2. guía para desplegar transformers en el Neural Engine
  3. Orion, publicado en arXiv el 6 de marzo de 2026
  4. ane de mdaiter
  5. latest.json de los benchmarks
  6. informe de paridad de Qwen2.5-0.5B
  7. informe de Qwen2.5-1.5B