Saltar al contenido principal

Cua en macOS VM: destrabar Metal y correr LLMs 11x mas rapido

·8 min read·Ignacio Avilés
compartir:TwitterLinkedIn

Cua en macOS VM: destrabar Metal y correr LLMs 11x mas rapido

Si venis corriendo modelos en tu Mac, ya te toco elegir entre dos mundos: o usas el hardware directo (bare-metal, Ollama o llama.cpp nativo) y resignas aislamiento, o metes el modelo adentro de una VM para tener snapshots, portabilidad y un limite estricto de recursos, y aceptas que la inferencia va a ir mal. Esa segunda opcion era la peor en Apple Silicon: una VM de macOS sobre Apple Silicon tipicamente caia al 10-15% del rendimiento bare-metal para inferencia de LLMs. El 11 de agosto el equipo de Cua publico una capa de compatibilidad que cierra esa brecha: 11.08x mas rapido en prompt processing y 16.36x mas rapido en token generation con TinyLlama 1.1B, llegando al 98% del bare-metal. Y con Gemma 4 12B cuantizado a 4-bit (6.98 GB), 99.59% en prompts y 94.82% en generacion. Es el primer resultado concreto y reproducible de "GPU passthrough usable" en macOS guests, y cambia como vamos a pensar agentes locales.

La pieza tecnica es chiquita en lineas de codigo pero conceptualmente rompe una pared que el Virtualization.framework de Apple venia sosteniendo en silencio. Vale la pena desarmarla.

Por que una VM de macOS sobre Apple Silicon era tan lenta

Cuando creas una VM con el Virtualization.framework de Apple en una Mac con chip M-series, el guest ve un dispositivo grafico paravirtualizado. Apple expone una GPU virtual que el host controla; el guest le envia comandos Metal, el host los ejecuta en el GPU fisico real. Es el mismo modelo de paravirtualizacion que usa QEMU con virtio-gpu, no es GPU passthrough al estilo VFIO de Linux.

El detalle que duele es este: cuando el guest Metal pregunta al dispositivo virtual por sus capacidades, este le responde con un set anclado en una familia antigua de Apple GPU (aproximadamente una Apple 5-era, con 32 KB maximos de threadgroup memory y sin SIMD-group matrix support). El runtime de Metal del guest recibe esa respuesta y elige kernels compatibles con esa familia, no con el hardware real que esta del otro lado. Llama.cpp consulta esas capabilities para elegir el path rapido, asi que termina ejecutando un kernel antiguo, conservador, en hardware perfectamente capaz de correr uno moderno.

En una linea: Apple virtualiza el dispositivo, no el hardware, y la respuesta que el guest recibe le miente sobre que kernels puede correr.

El resultado en la practica es que una VM de macOS corriendo llama.cpp con TinyLlama 1.1B procesaba prompts a una fraccion minima del bare-metal. Suficiente para que el proyecto Tart (otro CLI sobre Virtualization.framework) tenga un issue abierto desde hace meses preguntando si el framework va a dar GPU passthrough usable algun dia. Spoiler: Apple no va a cambiar el framework. La comunidad va a tener que sortear el limite por arriba, que es exactamente lo que Cua hizo.

Que construyo Cua: una capa delgada, no un fork del framework

Lo que Cua libero no es una modificacion del Virtualization.framework ni un hypervisor custom. Es una capa de compatibilidad con scope de proceso (process-scoped compatibility layer) que se carga dentro del guest macOS, intercepta las llamadas que hace llama.cpp a Metal, y las redirige a Metal fast paths modernos que el dispositivo virtual dice no soportar pero que el hardware fisico del host ejecuta sin problema.

Tres decisiones de diseno que vale la pena destacar:

  • Scope de proceso, no de sistema. No toca el kernel, no requiere kexts, no rompe SIP. Se carga como cualquier libreria dinamica y solo afecta al proceso que la linkea. Eso la hace trivial de revertir y de auditar, y permite correr el resto del sistema en el path Metal "stock" sin cambios.
  • Release research, no producto. La publican bajo la misma licencia permisiva que Lume y Cua para que la comunidad pueda reproducir los benchmarks y mapear que chips Apple Silicon, que versiones de macOS y que workloads Metal se benefician. Es deliberadamente incompleta: cubre el caso LLM, no promete ser un reemplazo general del stack grafico.
  • Incluye probe de capabilities y logs de benchmark. El repo viene con un binario que reporta exactamente que familia y features reporta el dispositivo virtual, y con los logs crudos de los benchmarks. Reproducibilidad como valor de diseño, no como promesa de marketing.

Lo que NO es: no es un bypass del sandbox del guest. El host sigue controlando el hardware. La capa expone paths que ya estan disponibles en el Metal runtime, no rompe el modelo de seguridad de Virtualization.framework.

Los benchmarks, con numero cerrado

Los dos casos publicados son los que importan para inferencia de LLMs en hardware modesto:

M1 Ultra, TinyLlama 1.1B, cuantizacion default de llama.cpp:

  • Prompt processing: 11.08x mas rapido que la VM stock. La VM desbloqueada llega al 98% del bare-metal.
  • Token generation: 16.36x mas rapido. Esa es la metrica que mas se siente cuando un agente espera el primer token de una respuesta larga.

M1 Ultra, Gemma 4 12B QAT Q4_0 (6.98 GB, modelo de este anio):

  • Prompt processing: 7.20x mas rapido, 99.59% del bare-metal.
  • Token generation: 14.54x mas rapido, 94.82% del bare-metal.

El segundo caso es el que mas me convence: un modelo de 12B cuantizado a 4-bit es exactamente el tamano de los agentes locales que mencionamos en Meta Muse Glimmer, y llegar al 95% del bare-metal dentro de una VM significa que podes correr ese mismo agente aislado, con snapshots, sin pagar el costo de rendimiento que hoy te hace elegir entre aislamiento y velocidad.

Para ponerlo en contexto: el post sobre Swiftlet trataba sobre como exprimir hardware limitado (4.3 GB de RAM para un 80B, cuantizacion agresiva y exploiting MoE). El angulo de Cua es el inverso: no comprime el modelo, destraba el hardware. Son problemas opuestos, complementarios. Un setup interesante seria Swiftlet + Cua: corres un 80B MoE en una VM de macOS con Metal destrabado, aprovechando la cuantizacion de Swiftlet para que entre en RAM y la capa de Cua para que la VM no sea un cuello de botella de GPU.

Que cambia para los que construimos agentes locales

Tres cosas concretas que se desbloquean con este approach, en orden de impacto practico:

Sandbox serio sin resignar rendimiento. Hoy, si queres correr un agente LLM que opera sobre tu filesystem, tu correo, tu calendario, lo metes en una VM o contenedor. Las VMs de macOS te dan snapshots, portabilidad entre Macs, limites de memoria y disco, y la posibilidad de tirarla y recrearla en un segundo. El precio era inferencia inutilizable. Con Cua, ese precio baja a 1-5% de overhead. El caso de uso "agente always-on dentro de una VM que reviso backups, code reviews, y triage de mails" pasa de ser teoricamente bueno a ser operacionalmente viable.

Reproducibilidad y portabilidad de benchmarks. Si tu agente corre dentro de una VM, podes versionar la VM, moverla entre Macs (M1, M2, M3, M4) y reproducir el mismo entorno exacto. Eso es un cambio cualitativo para evaluacion: en vez de "corri este benchmark en mi Mac un martes a las tres", podes decir "lo corri en la misma VM revision 47 sobre tres Macs distintas, mismos seeds, mismos resultados". Los benchmarks de inferencia locales dejan de ser anecdotal evidence y empiezan a ser data que podes defender ante un equipo.

Composicion con el stack agentico que ya existe. La capa de Cua no es opinionated sobre que modelo corre, que runtime usa, ni que framework agentico lo invoca. Llama.cpp carga la libreria, el resto del stack (Ollama, vLLM en Mac, frameworks de agentes) sigue funcionando encima. En particular, la combinacion con Cua Driver (el proyecto de computer-use agents del mismo equipo) es la que tiene mas sentido a corto plazo: agente que opera una interfaz grafica, aislado en una VM, con rendimiento casi bare-metal.

Lo que sigue faltando: GPU passthrough estilo VFIO (guest con acceso directo al dispositivo fisico, sin paravirtualizacion). Eso sigue siendo imposible en Apple Silicon a nivel framework, y no se ve en el roadmap. Mientras tanto, esta capa es el mejor workaround disponible y resuelve el 95% del problema para inferencia de LLMs.

Como probarlo hoy

El repo esta en github.com/trycua/cua, bajo blog/gpu-passthrough-macos-vms.md esta la nota tecnica completa y en el mismo repo los scripts de build, el probe de capabilities y los logs de benchmark. Para reproducir los numeros necesitarias una Mac M1/M2/M3/M4 con macOS reciente y Lume para crear la VM guest. El setup no es trivial todavia, pero es del tipo de proyecto que en un par de meses va a tener un brew install y un wrapper de Ollama que lo habilite por default.

Mi recomendacion si tenes una VM de macOS corriendo agentes: probalo sobre Gemma 4 12B Q4_0 (mismo tamano que los modelos de razonamiento abiertos que cubrimos en Glimmer) y medilo contra tu setup actual. Si la brecha con bare-metal te cae a menos de 5%, ya no hay excusa para no aislar el agente en produccion.

Y si lo que te importa es exprimir hardware limitado, Swiftlet sigue siendo la lectura recomendada. Los dos proyectos apuntan a problemas opuestos del mismo espacio: hacer que los modelos locales sean usables. La combinacion de los dos, en una VM, es lo mas cerca que estamos de un setup de agente local production-ready sobre Apple Silicon.

comentarios

$ subscribe --newsletter

Recibe los nuevos posts en tu email

Un email cuando publico algo nuevo. Sin ruido, sin relleno.

Sin spam. Baja en cualquier momento. Política de privacidad.