Qwen 3.8-27B en un M1 Ultra: De 12.1 a 20.3 tok/s por Medir Todo
5 experimentos controlados ejecutando Qwen 3.8-27B en un M1 Ultra de 128 GB: programador QoS pinning, decodificación especulativa MTP, un desafío GGUF, y un MoE que perdió por sobrecarga de despacho — ejecutado con Kimi K3 en pensamiento alto, con la evidencia powermetrics para cada llamada.
Developed by Robert E. Beckner III (Merlin) | rbeckner.com
Los pesos de Qwen3.8-27B llegaron por la mañana, y el mismo día comencé una aventura con Kimi K3 corriendo en modo de alto pensamiento. Inicié todo con una sola pregunta: ¿podría este 27B fresco y denso — fácilmente el modelo abierto más fuerte en su clase de tamaño — servir a mis agentes de codificación a velocidad real en un Mac Studio con un M1 Ultra, 20 núcleos de CPU, 128 GB de memoria unificada y 800 GB/s de ancho de banda de memoria? El conocimiento común dice que los modelos densos decodifican lentamente en Apple Silicon porque están limitados por el ancho de banda de memoria, y quería descubrir si esta cantidad de RAM y ancho de banda podrían romper el patrón. Los pesos MLX de 4 bits llegaron a 15 GB, el framework estaba actualizado, no había nada más corriendo en la caja, y el primer número fue 12.1 tokens por segundo. Se sintió incorrecto.
Había estado girando este tipo de experimento durante un tiempo. En marzo, trabajando con otra IA en mi laboratorio de refinamiento autónomo, rastreamos una pared de <20 tok/s para modelos de 30B en un M4 Max a la escasez de ancho de banda por paginación a nivel de OS — y esa investigación dejó un manual de juego: powermetrics primero, muestreo por clúster, fijación de memoria antes de culpar. La corrida esta vez comenzó por pura curiosidad: había oído hablar de las optimizaciones de Qwen3.8, y quería saber si funcionaría rápido fuera de la caja en esta máquina con tanta RAM. Solo a mitad del experimento la realidad limitada por el ancho de banda se hizo sentir. Lo que siguió fue una serie de experimentos controlados que diseñamos juntos — Kimi redactó las hipótesis y leyó los contadores de hardware a mi lado mientras yo mantenía mis manos sobre el metal. Cada experimento se construyó para acquitar o condenar una sola capa de la pila. Las victorias vinieron de 2 lugares que el folklore de inferencia local subestima: indicaciones del programador de CPU y decodificación especulativa. Las alternativas 2 de moda — una construcción GGUF servida por llama.cpp, y un modelo de mezcla de expertos que parecía invencible en papel — ambas perdieron en medición. Este artículo es la pista de evidencia completa que construimos juntos, porque el método resultó valer más que cualquier número individual.
El relevo de la red y el servidor fueron los primeros sospechosos, y ambos fueron inocentes#
La ruta de servicio tenía 3 saltos: mi estación de trabajo, un relevo ejecutivo basado en SSH, y el servidor del modelo en el loopback del Studio. Culpar al relevo habría sido fácil, así que lo medimos primero. Los propios tiempos del servidor indicaban 12.1 tok/s de decodificación; una prueba de generación en proceso sin servidor y sin relevo produjo 11.5 tok/s. El transporte costaba alrededor de 25% por sobrecarga por conexión, y el propio modelo era la capa lenta.
Un error de transporte valía la pena arreglar de todos modos, y es el tipo de detalle que cuesta horas si nunca lo has visto: un relevo Python TCP que usa un read(65536) con búfer se bloquea contra un protocolo local hablador, porque el lector con búfer espera un búfer lleno o EOF antes de devolver. Cambiar a read1(), que devuelve después de una sola lectura subyacente, hizo que el relevo se comportara. El síntoma parecía un bloqueo de red; la causa era la semántica de stdio.
Con el transporte exonerado, trabajamos a través de la capa habitual de sysctl. Elevar el límite de memoria cableada de GPU (iogpu.wired_limit_mb) no cambió nada con 128 GB de RAM libres, así que lo revertimos. El proceso Python era nativo arm64, MLX reportó la GPU como su dispositivo predeterminado, y powermetrics mostró la GPU con 54-62% de residencia activa dibujando 20-23 W durante la decodificación. Cada sospechoso en esta ronda salió libre — lo cual fue el resultado útil, porque apuntó la investigación a la CPU.
El programador había estacionado la inferencia en los núcleos de eficiencia#
Leer powermetrics por clúster de CPU en lugar de por máquina expuso el primer hallazgo real. Durante la decodificación, los clústeres de eficiencia funcionaban a 75-93% ocupados mientras los clústeres de rendimiento estaban inactivos a 1-12%. Cualquier cosa lanzada sobre SSH hereda una clase de calidad de servicio que macOS lee como trabajo de baja prioridad, y el programador estaba honrando esa pista manteniendo una carga de trabajo crítica de latencia en los núcleos lentos.
La corrección fue 1 línea de ctypes, ejecutada antes de cargar los pesos del modelo:
Ese pin, más la carga de la torre de texto del modelo a través de mlx-lm en lugar de la pila completa de visión, movió la decodificación en bruto de 11.5 a 15.1 tok/s — una ganancia del 31% por la colocación del programador y un cargador más ligero, sin cambio en el modelo. Más tarde supimos que el alcance del pin tiene un límite: mueve el hilo llamador, y los hilos de trabajo de MLX mantienen su propia clase de programación. Para este modelo el hilo principal llevaba suficiente trabajo para importar.
La decodificación especulativa entregó el salto que ningún sysctl pudo#
La mayor victoria individual provino de una característica que el modelo base ya poseía. Qwen3.8 se entrega con una cabeza de predicción multi-token — un pequeño módulo auxiliar que redacta varios tokens por delante — y el convertidor MLX silenciosamente descarta esos tensores 15 durante la conversión. Un paquete comunitario vuelve a alojar la cabeza como un redactor independiente de 253 MB, lo que me permitió emparejarla de nuevo con el modelo con el que fue entrenada.
El patrón de servicio es redactar-then-verificar: el redactor propone algunos tokens, el modelo completo los verifica en una pasada 1, y los tokens aceptados cuentan todos. Con --draft-model y --draft-kind mtp en el servidor, la aceptación de borradores midió 94 % en prompts de codificación y chat realistas, y la decodificación pasó de 15.1 a 20.3 tok/s del lado del servidor — 13.4 tok/s de extremo a extremo a través del relay, frente a 9.0. La GPU contó la historia confirmatoria: 77% residencia activa, todo a la plena 1296 MHz, consumiendo 48 W, con el clúster de rendimiento 97% ocupado. El prellenado mejoró en la misma actualización, de 14.7 tok/s a 18-55 según la forma del prompt.
Decode speed by configuration (tok/s, M1 Ultra, 27B-4bit)Chart data
Las publicaciones comunitarias colocan llama.cpp con un borrador especulativo en 25-32 tok/s para esta clase de modelo en M1 Ultra, cómodamente por delante de nuestros números MLX, así que le dimos al competidor un círculo justo: el Q4_K_M GGUF oficial, un modelo de borrador solo MTP coincidente, y una compilación actual de llama.cpp con aceleración Metal confirmada activa. La vía GGUF midió 9.2 tok/s de referencia y 12.2 con el borrador — aproximadamente 40% detrás de la pila MLX que se suponía debía derrocar.
llama.cpp sigue siendo una ingeniería excelente; la brecha probablemente reside en cómo los kernels Metal de cada tiempo de ejecución manejan este punto de control en este chip. La lección duradera es más simple: un número que alguien más midió en su máquina, su compilación y su punto de control es una hipótesis sobre el tuyo. Los 18 GB de pesos del competidor salieron de la máquina la misma tarde, y el binario de llama.cpp de 50 MB quedó para futuros revancha. He escrito sobre esta regla antes como benchmark-driven development — llevó SEOReport from heuristics to a product — y aquí salvó una migración.
Un MoE activo de 3B debería haber ganado, y el medidor de GPU explicó la pérdida#
El último desafiante tenía la teoría más fuerte — el mismo conocimiento común que habíamos decidido probar al principio. Los modelos densos decodifican lentamente en Apple Silicon porque cada token generado paga para leer casi todos los pesos; un modelo de mezcla de expertos con 35B parámetros totales activa solo alrededor de 3B por token, así que en una máquina limitada por ancho de banda su decodificación debería correr varias veces más rápido que un denso de 27B — cada token lee alrededor del 11% de los pesos. Descargamos el MoE de 4 bits y su borrador MTP correspondiente, calentamos el servidor y medimos 14.7 tok/s crudo: la misma velocidad que el modelo denso que se suponía debía humillar. Con decodificación especulativa alcanzó 19 tok/s en indicaciones realistas y 26.3 en texto altamente predecible — un empate contra el 20.3 del modelo denso, sin argumento de calidad para romper el empate.
powermetrics identificó el régimen en 1 muestra. Durante la decodificación MoE la GPU estaba en 0-3% de residencia activa mientras los clústeres de eficiencia se agotaban al 60-90%. El modelo gasta su presupuesto por token en trabajo del lado de la CPU, y la medición a nivel de operación mostró por qué: cada operación despachada cuesta 50-100 microsegundos de sobrecarga, y esta arquitectura híbrida — atención lineal GatedDeltaNet más 256 expertos detrás de un pequeño estado oculto de 2048 ancho — emite aproximadamente 78 operaciones por capa a través de 40 capas. Con tamaño de lote 1, la GPU termina cada kernel pequeño antes de que la CPU pueda encolar el siguiente. La máquina estaba limitada por despacho, y las cargas de trabajo limitadas por despacho no obtienen nada al leer menos pesos por token.
Diagram source
graph TB
A[Decodificación lenta
en lote 1] --> B{¿GPU ocupada
durante la decodificación?}
B -->|sí| C[Limitado por ancho de banda
menos bytes gana]
B -->|no| D[Limitado por despacho
menos operaciones gana]
C --> E[MoE ayuda
cuantificación más pequeña ayuda]
D --> F[Decodificación especulativa
ayuda más]
style E fill:transparent,stroke:#10B981,stroke-width:2px
style F fill:transparent,stroke:#3B82F6,stroke-width:2px
Para cerrar el bucle, demostramos que la ruta GPU en sí estaba saludable con un hog artificial: un bucle de multiplicación de matriz 8192³ alcanzó 10.8 TFLOPS en bfloat16 con la GPU en 97-100% de residencia y 68 W. La evidencia externa coincidió con el diagnóstico local — estimaciones independientes colocan esta clase MoE cerca de 21.7 tok/s en un M2 Ultra, y un problema OpenVINO documenta el mismo modelo perdiendo contra un denso de 8B en backends con rutas de despacho más débiles. La densa 27B con su redactor MTP mantuvo la ranura de producción, y 38,5 GB de MoE pesos fueron eliminados esa misma noche.
GPU active residency during decode (powermetrics, %)Chart data
La máquina ahora sirve Qwen 3.8-27B a 20.3 tok/s del lado del servidor, una mejora 68% sobre donde empezó el día, y el rendimiento más duradero es una lista de verificación que reutilizaremos en cada caja futura:
Decodificar con tamaño de lote 1 tiene 2 regímenes, limitado por ancho de banda y limitado por despacho, y una muestra de 2 segundos powermetrics identifica la tuya antes de que gastes una descarga en la corrección equivocada.
La decodificación especulativa es el control de servicio de mayor impacto para una caja de usuario único. Añadió 34% aquí y se compone con cada otra optimización, porque los lotes de verificación trabajan la GPU mientras el redactor absorbe los intervalos inactivos.
El programador QoS es un parámetro real de inferencia en macOS. Cualquier cosa lanzada sobre SSH debe fijar sus hilos deliberadamente, y el efecto de la fijación es verificable por clúster en lugar de por vibraciones.
Las afirmaciones de rendimiento comunitario viajan mal a través de puntos de control, compilaciones y chips. Una prueba local es más barata que una migración.
La eliminación es parte del flujo de trabajo. Cada desafiante que perdió dejó el disco dentro del día, lo que mantiene el próximo experimento honesto y la máquina ligera.
La parte satisfactoria de esta aventura es que las respuestas estaban todas en los contadores de hardware, esperando a que alguien hiciera preguntas precisas — y esta vez las hicimos juntos. Un modelo local que has medido vale más que un modelo más grande que has supuesto — el mismo retorno compuesto que obtengo al tratar ganancias de IA locales como infraestructura en lugar de trivia. Inicié todo el asunto en la mañana en que llegaron los pesos, y Kimi K3 se quedó en la lucha por cada experimento, cada lectura de contador, y esta redacción de las notas de laboratorio de la misma noche. El próximo experimento ya está en cola: los gráficos de decodificación compilados prometen colapsar el costo de despacho por operación que decidió el veredicto MoE, y cuando una versión MLX salga, el revancha tomará una tarde y 1 descarga.