Optimización de la inferencia en el dispositivo para Apple Silicon

Un motor local personalizado que mejora el rendimiento de prellenado y decodificación.

AutoresPerplexity Engineering

El cómputo híbrido en Apple silicon orquesta una tarea entre la inteligencia de vanguardia en la nube y un modelo local en la Mac. Los modelos en la nube se encargan de la investigación y el razonamiento, mientras que un modelo local trabaja con archivos y aplicaciones privados en la Mac.

Para que esta división del trabajo se sienta fluida, la inferencia local debe mantenerse al día con el resto de la tarea. Eso requiere un motor que pueda procesar prompts rápidamente y sostener una alta tasa de generación de tokens.

Lily, nuestro motor de inferencia local y ligero, está diseñado específicamente para Apple silicon y Qwen3.6-35B-A3B, con optimizaciones separadas para el prellenado y la decodificación. El código abierto del motor se publicará próximamente.

Introducción

Una forma común de ejecutar LLMs en una Mac es con MLX, el marco de aprendizaje automático de código abierto de Apple para Apple silicon. Su biblioteca complementaria, MLX-LM, añade los componentes necesarios para cargar y generar texto con una amplia gama de modelos de lenguaje. Juntos, MLX y MLX-LM proporcionan una pila lista para usar y de propósito general para la inferencia de LLMs locales.

Qwen3.6-35B-A3B es un modelo híbrido y disperso: utiliza enrutamiento de mezcla de expertos (MoE) y combina estados recurrentes de tamaño fijo con atención completa. Estas opciones arquitectónicas reducen la cantidad de cómputo requerido, pero también generan cargas de trabajo irregulares. Los tokens se enrutan a diferentes pesos de expertos y los estados recurrentes son de naturaleza secuencial.

MLX-LM ya selecciona kernels optimizados para las fases de inferencia y las formas comunes de carga de trabajo, pero sus operaciones reutilizables deben admitir muchas arquitecturas de modelos. Un motor dedicado a Qwen puede especializarse a nivel de modelo y de entorno de ejecución, coordinando los kernels, el movimiento de datos y la programación en torno a la estructura fija del modelo.

Lily implementa esta especialización de extremo a extremo en un solo proceso. Un entorno de ejecución en Rust carga el punto de control del modelo y gestiona el estado de la sesión y el bucle de generación; una API de completado de chat compatible con OpenAI acepta solicitudes y transmite tokens; y los kernels de Metal personalizados ejecutan operaciones específicas de Qwen. Ni PyTorch ni MLX se encuentran en la ruta de ejecución.

Dónde se ubican la generalidad y la especialización en las dos pilas de inferencia. MLX-LM describe el modelo como operaciones de matriz MLX componibles, las cuales MLX programa mediante núcleos reutilizables. En su lugar, Lily sitúa la estructura del modelo, los planes de ejecución específicos de la fase y la selección de núcleos en un único tiempo de ejecución en Rust construido en torno a Qwen y Apple Silicon.
Dónde se ubican la generalidad y la especialización en las dos pilas de inferencia. MLX-LM describe el modelo como operaciones de matriz MLX componibles, las cuales MLX programa a través de kernels reutilizables. En su lugar, Lily coloca la estructura del modelo, los planes de ejecución específicos de cada fase y la selección de kernels en un único entorno de ejecución en Rust construido en torno a Qwen y Apple silicon.

Medimos el rendimiento de prellenado y decodificación por separado. El rendimiento de prellenado captura la rapidez con la que el motor procesa el prompt; el rendimiento de decodificación captura la rapidez con la que genera los tokens de salida.

Evaluamos el rendimiento de Qwen3.6-35B-A3B en una sola MacBook Pro con un chip M5 Max con una GPU de 40 núcleos y 128 GB de memoria unificada. A lo largo de diez longitudes de prompts para el prellenado y diez longitudes de contexto para la decodificación, desde 256 hasta 128K tokens (K = 1,024), el motor alcanza un promedio de 1.23× el rendimiento de prellenado de MLX-LM y 1.35× su rendimiento de decodificación. Con un prompt de 4K tokens y un contexto de decodificación de 4K tokens, el motor personalizado alcanza 5,749.9 tokens de prellenado por segundo y 186.6 tokens de decodificación por segundo, en comparación con los 4,737.5 y 140.9 de MLX-LM. A lo largo de una sesión multi-turno, este ahorro de tiempo se acumula con cada llamada adicional al modelo.

Rendimiento medio aritmético en diez longitudes con la misma ponderación, de 256 a 128K tokens. Lily promedia 4,156 tokens de prellenado por segundo frente a los 3,388 de MLX-LM (1.23×), y 170.0 tokens de decodificación por segundo frente a 126.4 (1.35×). El prellenado varía según la longitud del prompt; la decodificación varía según la longitud del contexto.
Rendimiento promedio aritmético en diez longitudes con el mismo peso, desde 256 hasta 128K tokens. Lily promedia 4,156 tokens/s de prellenado frente a 3,388 de MLX-LM (1.23×), y 170.0 tokens/s de decodificación frente a 126.4 (1.35×). El prellenado varía la longitud del prompt; la decodificación varía la longitud del contexto.

A continuación, explicamos cómo la arquitectura de Qwen crea oportunidades de optimización específicas del modelo en Apple silicon. Luego, recorremos los cambios resultantes en el prellenado y la decodificación. También cubrimos en qué punto la optimización adicional deja de ser rentable antes de cerrar con una comparación de extremo a extremo frente a MLX-LM.

Oportunidades de optimización específicas para Qwen en Apple silicon

Qwen crea tres formas de carga de trabajo distintas

Qwen3.6-35B-A3B contiene 35 mil millones de parámetros, pero activa solo unos 3 mil millones para cada token. Un enrutador califica 256 subredes de expertos y selecciona ocho, junto con un experto compartido que procesa cada token. Este diseño de MoE disperso reduce el cómputo pero produce un trabajo desigual: los expertos reciben diferentes números de tokens, y cada token requiere pesos de una combinación diferente de expertos.

Qwen también combina 10 capas de atención completa con 30 capas de Gated DeltaNet. Estos dos tipos de capas retienen información anterior de diferentes maneras.

Las capas de atención utilizan atención de consultas agrupadas (GQA). Qwen tiene 16 cabezas de consulta y dos cabezas de clave-valor (KV), con ocho cabezas de consulta compartiendo cada cabeza KV. Compartir hace que el caché KV sea más pequeño y permite que los datos en caché se reutilicen en todas las cabezas de consulta. El caché todavía almacena nuevas claves y valores para cada token, por lo que cada paso de decodificación lee más datos a medida que el contexto crece.

En su lugar, Gated DeltaNet comprime la información anterior en un estado recurrente de tamaño fijo. Una puerta aprendida controla cuánto del estado existente se debe retener, mientras que una actualización delta incorpora información del token actual. El modelo define estas actualizaciones de forma recurrente, por lo que cada token depende del estado producido por el token precedente. Sin embargo, durante el prellenado, un motor puede evaluar el mismo cálculo de dos maneras. Puede escanear los tokens directamente mientras lleva el estado hacia adelante, o reorganizar las actualizaciones en bloques que exponen más operaciones de matriz y paralelismo a nivel de token. Qué enfoque sea más rápido depende de las dimensiones del modelo, la carga de trabajo y el hardware.

En conjunto, estas estructuras crean tres patrones computacionales: grupos de expertos desiguales, atención sobre un caché en crecimiento y una recurrencia de tamaño fijo que se puede evaluar directamente o en bloques.

Apple silicon proporciona diferentes rutas para diferentes cargas de trabajo

El prellenado procesa muchas filas de activación de tokens de un prompt a la vez. La carga de trabajo local considerada aquí normalmente decodifica una solicitud a la vez (lote 1) y procesa una nueva fila por paso. Esta diferencia cambia la forma en que se utilizan los mismos pesos del modelo. El prellenado puede reutilizar cada bloque de pesos en cientos o miles de filas. La decodificación en gran medida no puede hacerlo, ya que cada nuevo token requiere otra pasada a través de los pesos.

Apple silicon coloca la CPU y la GPU detrás de la memoria unificada, un único grupo de memoria física accesible para ambas. Esto permite que el modelo permanezca residente sin mantener una copia separada en la GPU, pero no hace que el movimiento de datos sea gratuito. La lectura de pesos y valores intermedios sigue consumiendo ancho de banda de memoria, mientras que los registros y otros almacenamientos en chip son más rápidos pero mucho más pequeños.

La GPU del M5 también proporciona diferentes rutas de cómputo. Las capas lineales del prellenado utilizan la multiplicación general de matrices (GEMM), aplicando una matriz de pesos a muchas filas a la vez. Las GEMM compatibles pueden utilizar el Acelerador Neuronal en cada núcleo de GPU a través de operaciones de tensores de Metal 4. En su lugar, la decodificación batch-1 utiliza la multiplicación general de matriz-vector (GEMV), aplicando los mismos pesos a una fila. Con poca reutilización de pesos, la GEMV está limitada principalmente por el ancho de banda de la memoria y se adapta mejor a las unidades aritméticas lógicas (ALU) vectoriales de la GPU que a los Aceleradores Neuronales diseñados para operaciones matriciales con mayor reutilización de datos.

Estas rutas de ejecución no son exclusivas de Lily. MLX opera sobre la misma memoria unificada y selecciona kernels de matrices y vectores optimizados según la forma de la carga de trabajo. La implementación de Qwen en MLX-LM ya agrupa el trabajo de los expertos, evalúa Gated DeltaNet con un kernel Metal recurrente fusionado y utiliza atención consciente de GQA. Estas capacidades son el punto de partida compartido para una inferencia eficiente de Qwen en Apple silicon.

Estrategia de optimización

El alcance más reducido de Lily le permite coordinar estas rutas de ejecución compartidas en torno a la arquitectura y dimensiones exactas de Qwen. Utiliza rutas de GPU específicas para cada fase, mapea las cargas de trabajo de expertos, recurrentes y de atención de Qwen para minimizar el movimiento de datos, y selecciona kernels y diseños a partir de la forma de la carga de trabajo medida. La estrategia consta de tres partes:

  1. Adaptar la ruta de la GPU a la fase de inferencia. Utilizar la ejecución orientada a matrices cuando el prellenado pueda reutilizar pesos en muchas filas, y la ejecución orientada a vectores cuando la decodificación batch-1 procesa una fila a la vez.
  2. Mapear la estructura de Qwen en la GPU minimizando el movimiento de datos. Mantener los pesos comprimidos hasta que se utilicen, organizar el trabajo de los expertos enrutados sin volver a la CPU, retener el estado de Gated DeltaNet en el chip a través de su escaneo recurrente y reutilizar los datos KV compartidos por la atención de consultas agrupadas.
  3. Adaptar los kernels a la forma de la carga de trabajo. Dentro de cada fase, seleccionar los tamaños de bloque, los diseños de ejecución y las rutas de atención a partir del recuento de filas disponible, la distribución de las filas entre los expertos, las dimensiones de la operación y la longitud actual del contexto.

Las siguientes secciones explican estas opciones. En el caso de las optimizaciones evaluadas en ablaciones coincidentes en un M5 Max, estimamos sus efectos comparando configuraciones de motor por lo demás idénticas que solo difieren en la optimización bajo estudio. Debido a que estos experimentos comparan versiones de nuestro motor contra sí mismo, explican los mecanismos en lugar de descomponer los resultados finales frente a MLX-LM.

Prellenado: reutilizar pesos y mantener el enrutamiento en la GPU

El prellenado expone muchas filas de tokens a la vez, pero Qwen enruta esas filas de manera desigual entre los expertos y actualiza el estado recurrente a lo largo de la secuencia. Sus optimizaciones se dividen en tres grupos: organizar el trabajo de los expertos dispersos en torno a las filas enrutadas, mantener el escaneo de Gated DeltaNet en el chip y dividir los prompts largos en fragmentos acotados.

Ejecución y residencia de datos para un fragmento de prellenado acotado a través de una capa Qwen. La atención amplía la caché KV, mientras que Gated DeltaNet transporta su estado recurrente de trabajo en registros. Los metadatos de enrutamiento de expertos permanecen en la GPU, los pesos Q4 se mantienen empaquetados hasta que se des-cuantizan dentro del GEMM agrupado, y las activaciones temporales se limitan al fragmento actual.
Ejecución y residencia de datos para un fragmento de prellenado acotado a través de una capa de Qwen. La atención amplía el caché KV, mientras que Gated DeltaNet transporta su estado recurrente de trabajo en los registros. Los metadatos de enrutamiento de expertos permanecen en la GPU, los pesos Q4 se mantienen empaquetados hasta que se desquantizan dentro de la GEMM agrupada, y las activaciones temporales se limitan al fragmento actual.

Optimizar el cómputo de expertos dispersos

Debe desquantizar los pesos durante la multiplicación de matrices

El punto de control Qwen3.6-35B-A3B utiliza cuantización afín por grupos de 4 bits. Cada peso se almacena como un código entero de 4 bits, mientras que cada grupo de 64 pesos comparte una escala y un sesgo en bfloat16 que se utilizan para reconstruir sus valores. Esto reduce el modelo de 35 mil millones de parámetros de aproximadamente 70 GB de pesos en bfloat16 a un punto de control de 19.4 GB, lo que hace práctico mantener el modelo residente en la Mac.

La operación de tensores de Metal 4 utilizada para la multiplicación de matrices consume operandos bfloat16 en lugar de la representación empaquetada de 4 bits. Antes de la multiplicación, la GPU debe reconstruir los pesos en bfloat16. La GEMM agrupada optimizada en Lily realiza esta conversión un pequeño bloque de pesos a la vez y retiene el resultado en la memoria de threadgroup del chip solo el tiempo suficiente para multiplicarlo por las filas de activación enrutadas. La acumulación utiliza un punto flotante de 32 bits, y la salida se escribe en bfloat16. La matriz de pesos expandida completa nunca se crea en la memoria unificada.

En la ablación, la desquantización se ejecuta como una operación separada: expande los pesos de 4 bits en una matriz bfloat16 en la memoria unificada, tras lo cual el kernel de matriz lee dicha matriz de vuelta. Con un prompt de 512 tokens, mover la desquantización a la GEMM agrupada incrementó el rendimiento del prellenado de extremo a extremo en un 77.4 % al eliminar esta escritura y lectura intermedias.

Mantener el enrutamiento de expertos en la GPU

La GEMM agrupada requiere que las filas de activación asignadas a cada experto se almacenen juntas. Después de seleccionar ocho expertos por token, un histograma cuenta cuántas asignaciones se destinaron a cada experto. Un escaneo de prefijos convierte esos recuentos en desplazamientos iniciales, un paso de dispersión coloca las filas en sus grupos de expertos, y un mapa de bloques enumera los bloques de matriz de tamaño fijo que la GEMM agrupada debe procesar.

La ruta optimizada mantiene toda esta secuencia en un solo búfer de comandos (un lote ordenado de operaciones de GPU) para cada fragmento de prompt. En su lugar, una ablación hace una pausa para que la CPU pueda inspeccionar los elementos intermedios de enrutamiento y enviar la siguiente operación. Mantener el histograma y el escaneo de prefijos en la GPU añade dos kernels, pero elimina la sincronización entre CPU y GPU dentro de cada capa MoE.

Con un prompt de 512 tokens, habilitar el enrutamiento residente en la GPU incrementó el prellenado de extremo a extremo en un 89 %. Esto también muestra por qué el recuento de kernels por sí solo puede ser engañoso: la ruta más rápida lanza más kernels, pero nunca espera a la CPU dentro de la capa.

Ajustar el tamaño de bloque a la carga del experto

Con un prompt de 2K tokens, enrutar cada token a ocho de los 256 expertos produce 16,384 asignaciones de token-experto, o un promedio de 64 filas de activación por experto. La distribución real es desigual: algunos expertos reciben muchas filas, mientras que otros reciben pocas.

La GEMM agrupada divide la salida de cada experto en bloques, que son pequeños bloques rectangulares de la salida de una multiplicación de matrices. Cada bloque se asigna a un threadgroup de GPU. En las GPUs de Apple silicon, un threadgroup contiene uno o más simdgrouops, cada uno de los cuales consta de 32 hilos que ejecutan instrucciones de forma sincronizada.

Los bloques más grandes reparten el costo de configuración entre más filas y exponen más trabajo en paralelo, pero una parte de un bloque grande permanece inactiva cuando un experto recibe solo unas pocas filas. Por lo tanto, el tamaño del bloque y el recuento de simdgrouops están acoplados.

Una ablación fija el bloque en 16 filas. Frente a ese control, habilitar el bloque de 32 filas con cuatro simdgrouops mejoró el prellenado de extremo a extremo en un 13.2 % con 2K tokens.

Mantener el estado recurrente en el chip

Durante el prellenado, cada capa de Gated DeltaNet escanea el prompt en orden mientras lleva su estado recurrente hacia adelante. Con la residencia en registros deshabilitada, la ablación utiliza un escaneo por bloques. Con un prompt de 2K tokens, esa ruta mueve 256 MiB (mebibytes) de estado por capa y detiene repetidamente los hilos cooperativos en barreras (puntos de sincronización donde todos los hilos participantes deben esperar unos a otros).

El estado recurrente es una matriz. El kernel optimizado asigna cada columna a un simdgroup. El simdgroup divide la columna entre sus hilos, carga la columna en sus registros una sola vez y transporta el estado a lo largo de todo el escaneo. Los hilos intercambian resultados intermedios mediante operaciones de simdgroup en lugar de memoria de threadgroup (almacenamiento en chip compartido entre un threadgroup). El estado completado se vuelve a escribir solo después del escaneo.

El estado y su puerta utilizan un formato de punto flotante de 32 bits porque los pequeños errores de redondeo se acumulan en las actualizaciones secuenciales. Las activaciones de consulta y clave permanecen en bfloat16.

Con un prompt de 2K tokens, habilitar el escaneo residente en registros mejoró el prellenado de extremo a extremo en un 5.6 %. Las GEMM de expertos representaron alrededor del 90 % del tiempo de prellenado. El escaneo secuencial no expone suficiente trabajo matricial reutilizable como para beneficiarse de los Aceleradores Neuronales.

Limitar la memoria temporal con la fragmentación de prompts

El entorno de ejecución procesa un prompt largo como una secuencia de fragmentos acotados en lugar de mantener datos temporales para cada token del prompt en la memoria al mismo tiempo. Los pesos del modelo permanecen residentes en la memoria unificada, mientras que el estado recurrente y el caché KV transportan el contexto de un fragmento al siguiente. No se descarta ningún contexto anterior.

Sin la fragmentación, las matrices de activación temporal crecen con todo el prompt y compiten con los pesos del modelo, el estado recurrente y el caché KV por la memoria unificada. La fragmentación mantiene vivos los valores temporales de un solo segmento a la vez, y luego libera o reutiliza ese almacenamiento antes de procesar el siguiente segmento. Esto limita la memoria de trabajo máxima y permite que el motor procese prompts más largos sin cambiar la salida del modelo.

El prellenado fragmentado es popular en muchos motores y es crucial para servir trayectorias largas multi-turno en estos entornos con restricciones de memoria. El tiempo total de prellenado para las capas de atención sigue siendo cuadrático respecto a la longitud del prompt, con cierta sobrecarga adicional debido a las repetidas cargas KV de fragmentos anteriores.

Decodificación: minimizar los bytes movidos por token

La decodificación batch-1 procesa una nueva fila a la vez. Con poca reutilización de pesos, su rendimiento depende principalmente de cuántos bytes mueve el motor para cada token. Los cambios en la decodificación se dividen en cuatro grupos: optimizar la ruta de pesos de una sola fila, mantener cada paso en la GPU, reducir el tráfico intermedio y de estados, y leer el caché de atención de manera eficiente.

Flujo de datos para un paso de decodificación en lote 1 y dos mecanismos que reducen el tiempo inactivo y el tráfico de caché. (A) La GPU transmite los pesos Q4 y el estado del modelo a través de la atención, Gated DeltaNet, el enrutamiento y los núcleos de expertos fusionados; luego, escribe el token seleccionado directamente en la ranura de entrada del siguiente paso mientras envía una copia a la CPU. (B) La programación consciente de dependencias permite que los núcleos independientes se solapen. (C) El empaquetado GQA permite que cuatro cabezas de consulta compartan cada carga de fila KV, lo que reduce ocho solicitudes independientes a dos cargas compartidas.
Flujo de datos para un paso de decodificación batch-1 y dos mecanismos que reducen el tiempo de inactividad y el tráfico de caché. (A) La GPU transmite los pesos Q4 y el estado del modelo a través de la atención, Gated DeltaNet, el enrutamiento y los kernels de expertos fusionados, para luego escribir el token seleccionado directamente en la ranura de entrada del siguiente paso mientras envía una copia a la CPU. (B) La programación consciente de dependencias permite que los kernels independientes se superpongan. (C) El empaquetado GQA permite que cuatro cabezas de consulta compartan cada carga de fila KV, reduciendo ocho solicitudes independientes a dos cargas compartidas.

Optimizar la ruta de peso de una sola fila

MLX ya despacha trabajo de una sola fila a kernels especializados de matriz-vector. Como Lily no utiliza MLX, el entorno de ejecución personalizado debe proporcionar la misma estrategia básica. Nuestra GEMV paralela por filas está diseñada para una fila de activación. Un simdgroup coopera en la salida mientras lee diferentes partes de la matriz de pesos en paralelo.

Mantener cada paso de decodificación en la GPU

Mantener la transferencia de tokens en la GPU

Cada paso de decodificación termina seleccionando el siguiente token; el siguiente paso comienza con ese token como entrada. Enviar la selección a la CPU y luego de vuelta a la GPU añade un punto de sincronización a cada token. En su lugar, nuestro entorno de ejecución alterna entre dos búferes de comandos y dos ranuras de tokens residentes en la GPU. La GPU selecciona el token con la puntuación más alta y escribe su ID de token directamente en la ranura de entrada para el siguiente paso de decodificación, mientras la CPU prepara el trabajo posterior.

Superponer trabajo de GPU independiente

En un paso de decodificación batch-1 registrado, la generación de un token lanzó 795 kernels de GPU. Sus dependencias formaron 555 etapas secuenciales, lo que dejó algunos kernels libres para ejecutarse de forma simultánea. No obstante, el modo de ejecución serial de Metal ejecutó cada kernel en orden.

La ruta de decodificación optimizada registra las dependencias de datos reales en una pasada de Metal concurrente. Los lanzamientos de kernels independientes pueden ejecutarse al mismo tiempo cuando los recursos de la GPU lo permiten. Se inserta una barrera solo cuando el trabajo posterior requiere un resultado anterior.

Reducir el tráfico intermedio y de estados

Los kernels separados a menudo materializan un elemento intermedio: un kernel escribe un resultado temporal en la memoria y el siguiente lee el resultado de vuelta. La ruta de decodificación optimizada fusiona cuatro cadenas: las dos proyecciones de entrada de expertos con su activación con puertas; la proyección de salida de expertos con su puntuación de enrutamiento y el resultado del experto compartido; la preparación de consultas y claves antes de la atención; y la actualización recurrente con su normalización. Cada kernel fusionado mantiene los valores temporales en los registros en lugar de enviarlos a través de la memoria.

La fusión también acorta el gráfico de dependencias: cuando una escritura intermedia desaparece, también lo hace la barrera que protegía a su consumidor.

Leer el caché de atención de manera eficiente

Coalescer las lecturas del caché de atención

La atención lee claves y valores del caché KV durante cada paso de decodificación. En la ablación, los hilos de GPU vecinos no siempre solicitan bytes vecinos, lo que obliga al sistema de memoria a atender más transacciones separadas. Habilitar las cargas coalescentes hace que los hilos adyacentes soliciten bytes adyacentes para que el hardware pueda combinar sus lecturas.

En la configuración bfloat16, la coalescencia incrementó el ancho de banda de claves de 33.8 a 47.9 GB/s, incrementó el ancho de banda de valores de 42.0 a 61.8 GB/s y mejoró la decodificación de extremo a extremo en un 2.1 % con un contexto de 3,840 tokens.

Empaquetar las cabezas de consulta para reutilizar las filas KV

La atención de consultas agrupadas (GQA) permite que ocho cabezas de consulta compartan una cabeza KV. En la ablación, cada cabeza de consulta se ejecuta en un simdgroup separado, por lo que las ocho solicitan de manera independiente la misma fila KV almacenada en caché. El kernel optimizado agrupa cuatro cabezas de consulta en un solo threadgroup, el cual carga cada fila KV una sola vez y la reutiliza en cuatro cálculos de atención. Un segundo threadgroup maneja las cuatro cabezas restantes.

Esta técnica, comúnmente llamada empaquetado GQA, realiza la misma aritmética y produce bytes de salida idénticos al tiempo que reduce ocho solicitudes KV independientes a dos cargas compartidas. Frente a la ablación no empaquetada, mejoró el rendimiento de decodificación de extremo a extremo en un 23.8 % con un contexto de 32K tokens.

Cambiar los diseños de atención en contextos largos

Cada paso de decodificación en una capa de atención completa escanea el caché KV existente. Un diseño de bloques fijos divide dicho caché en partes iguales que la GPU puede procesar en paralelo. Su programación adicional no vale la pena cuando el caché es pequeño, pero el diseño de bloques fijos equilibra el trabajo de manera más uniforme a medida que el contexto crece.

Para este modelo, el entorno de ejecución mantiene la ruta de atención general por debajo de los 32K tokens y utiliza la ruta de bloques fijos a partir de los 32K tokens. El cambio se aplica cuando cada cabeza tiene 256 valores y ocho cabezas de consulta comparten una cabeza KV; otras formas permanecen en la ruta general. Una ablación deshabilita este cambio y utiliza siempre la ruta general. Habilitar la ruta de bloques fijos mejoró la decodificación de extremo a extremo en un 7.7 % a 32K, un 27.4 % a 64K y un 40.2 % a 128K.

Límites de una mayor optimización

Algunos cambios mejoraron una operación aislada, pero no mejoraron la inferencia de extremo a extremo.

La decodificación especulativa (que utiliza un modelo más pequeño para proponer tokens que el modelo completo debe verificar) hizo que la decodificación batch-1 fuera un 18 % más lenta. La verificación procesó grupos de dos a cinco filas, una forma ineficiente para este hardware, y las filas a menudo seleccionaban diferentes expertos, lo que aumentaba la cantidad de datos de peso de expertos leídos. Reducir el vocabulario de salida del redactor mejoró el rendimiento del redactor entre un 4.7 y un 5.1 %, pero no hizo que el bucle especulativo completo fuera más rápido. Este resultado es específico de la carga de trabajo: nuestro despliegue por lotes de Qwen en Blackwell utiliza la decodificación especulativa en diferentes condiciones.

Otros experimentos incluyeron reducir los lanzamientos de GPU, superponer fases completas, usar bloques de prellenado más grandes, aplicar una fusión más amplia, acelerar el enrutador y combinar la proyección de salida con la selección de tokens. Ninguno mejoró el ciclo de inferencia completo.

Las mediciones de los límites del hardware también mostraron poco margen restante en las operaciones principales de prellenado y decodificación. Las GEMM y GEMV de MoE alcanzaron el 97.9 % y el 90.3 % de las tasas de lectura de pesos sostenidas más rápidas para sus patrones de acceso. Eliminar la aritmética de la GEMV dispersa cambió el rendimiento en apenas un 0.2 %, lo que confirma que las lecturas de pesos, y no el cómputo, eran el recurso limitante. De manera similar, la multiplicación de matrices del prellenado alcanzó el 93 % del límite teórico de matrices de forma aislada y entre el 80 y el 86 % dentro de los modelos probados.

Rendimiento de extremo a extremo

La comparación de extremo a extremo carga bytes de puntos de control de 4 bits idénticos en ambos motores y ejecuta una solicitud a la vez en un M5 Max de 40 núcleos y 128 GB. Dentro de cada ronda, los dos motores se ejecutan en orden alterno para reducir el sesgo de la carga en segundo plano y los cambios en la temperatura del chip. Comparamos frente a la ruta de generación directa más rápida de MLX-LM, no su servidor, por lo que la medición se centra en la ejecución del modelo en lugar de en la sobrecarga del servicio.

El barrido abarca diez longitudes de prompts para el prellenado y diez longitudes de contexto para la decodificación, desde 256 hasta 128K tokens. El rendimiento del prellenado primero aumenta a medida que el motor reparte los costos fijos de configuración entre más tokens. El prellenado alcanza su punto máximo alrededor de un prompt de 4K tokens, y luego disminuye porque las diez capas de atención completa realizan más trabajo a medida que el prompt crece. La decodificación se mantiene casi plana en contextos cortos y disminuye una vez que la lectura del caché KV en crecimiento se vuelve significativa. El motor personalizado es más rápido en cada longitud registrada.

Debido a que la ejecución especializada puede cambiar el orden de las operaciones en punto flotante, también verificamos la consistencia numérica frente a MLX-LM. En una comparación guiada por el profesor (teacher-forced), ambos motores predijeron el siguiente token a partir del mismo prefijo de referencia en cada una de las 192 posiciones, evitando que las diferencias anteriores afectaran las entradas posteriores. La perplejidad de Lily fue solo un 0.04 % mayor, y seleccionó el mismo token mejor clasificado en el 96.35 % de las posiciones probadas.

Rendimiento de prellenado según la longitud del prompt y rendimiento de decodificación según la longitud del contexto para Qwen3.6-35B-A3B Q4, lote 1, en un M5 Max de 40 núcleos y 128 GB. En diez longitudes, desde 256 hasta 128K tokens, Lily es más rápido en cada punto registrado: de 1.12 a 1.42× el rendimiento de prellenado de MLX-LM y de 1.31 a 1.37× su rendimiento de decodificación. La comparación utiliza la ruta de generación directa más rápida de MLX-LM. Ambos ejes horizontales utilizan escalas logarítmicas; ninguno de los ejes verticales comienza en cero.
Rendimiento de prellenado por longitud de prompt y rendimiento de decodificación por longitud de contexto para Qwen3.6-35B-A3B Q4, lote 1, en un M5 Max de 40 núcleos y 128 GB. A lo largo de diez longitudes desde 256 hasta 128K tokens, Lily es más rápido en cada punto registrado: de 1.12 a 1.42× el rendimiento de prellenado de MLX-LM y de 1.31 a 1.37× su rendimiento de decodificación. La comparación utiliza la ruta de generación directa más rápida de MLX-LM. Ambos ejes horizontales utilizan escalas logarítmicas; ninguno de los ejes verticales empieza en cero.

Diseñado para la plataforma local

Apple silicon no es una GPU de centro de datos más pequeña. Es una plataforma de inferencia local completa con sus propias características de hardware y software. La memoria unificada otorga a un solo nodo un límite muy alto en cuanto a la cantidad de modelos y estados que puede albergar. Los Aceleradores Neuronales del M5 absorben el trabajo de matrices densas en el prellenado. Las ALU vectoriales se encargan del resto, que está limitado por el ancho de banda y tiene una baja reutilización, durante la decodificación.

Qwen añade más oportunidades de especialización: mantener el enrutamiento de expertos y el estado recurrente en la GPU, eliminar elementos intermedios innecesarios, superponer trabajo independiente, reutilizar datos KV compartidos y adaptar los kernels a la forma de la carga de trabajo.

Con una optimización específica para el modelo y la plataforma, una sola Mac puede ejecutar un modelo disperso grande de manera eficiente. El trabajo futuro ampliará la cobertura entre modelos, chips y cargas de trabajo de servicio, y transformará los mecanismos validados aquí en una configuración en una directiva de tiempo de ejecución más general.

El principio más amplio es adaptar el motor tanto a la arquitectura del modelo como a las rutas específicas de cómputo y memoria del hardware. A medida que los modelos de pesos abiertos de vanguardia y el hardware evolucionen, la inferencia local de alto rendimiento dependerá cada vez más de motores adaptados a ambos en lugar de aquellos que abstraen sus diferencias.