1 puntos por GN⁺ 2025-05-07 | 1 comentarios | Compartir por WhatsApp
  • Anukari es un sintetizador físico 3D en tiempo real, así que necesita calcular modelos grandes de resortes y masas en la GPU, pero en macOS con Apple silicon, si el reloj de la GPU no sube lo suficiente, es difícil cumplir los requisitos de latencia de audio
  • La forma en que los DAW llaman al plugin en cada bloque del búfer de audio, combinada con las heurísticas de administración de energía de macOS, puede hacer que la GPU parezca estar descansando entre bloques y se quede en un estado de bajo rendimiento
  • En el profiler de Metal de Xcode Instruments, al poner Performance State en Maximum funciona correctamente, y en Minimum empeora mucho, confirmando que el cuello de botella principal es la velocidad del reloj de la GPU
  • Por ahora usan un pequeño spin kernel para elevar artificialmente la carga de la GPU, un workaround de “waste makes haste”, pero en algunos equipos Apple Pro/Max el problema sigue presente
  • El desarrollador pide al equipo de Apple Metal una extensión de GPU para Audio Workgroup, una opción de sensibilidad en tiempo real para MTLCommandQueue, o que indiquen una solución existente; además, considera que en Windows no hace falta este mismo spin loop

El problema de rendimiento de GPU en macOS que sufre Anukari

  • Anukari 3D Physics Synthesizer simula en tiempo real grandes modelos físicos de resortes y masas para generar audio
  • Para soportar una cantidad importante de objetos físicos necesita la GPU, y el código de física está más cerca de un cuello de botella de ALU que de memoria
  • El estado mutable de la simulación se guarda en la memoria de threadgroup de la GPU
    • Es una estructura parecida a una caché L1 asignada manualmente, así que es muy rápida
  • La forma de uso habitual es como plugin AU o VST3 dentro de un DAW como Pro Tools o Ableton
    • El DAW llama a Anukari en cada bloque del búfer de audio
    • Anukari ejecuta un kernel de simulación física en la GPU para cada bloque, espera el resultado y luego regresa
  • Los bloques del búfer de audio pueden absorber la latencia de planificación del kernel de GPU repartiéndola entre varias muestras, pero el tiempo de ejecución del kernel en sí sigue siendo decisivo

El choque entre la administración de energía de macOS y el audio en tiempo real

  • Apple silicon puede bajar la frecuencia de reloj del chip para ahorrar energía, y macOS mantiene un reloj bajo cuando considera que la demanda de procesamiento es baja
  • La forma en que Anukari corre dentro del DAW no encaja bien con la manera en que macOS estima la demanda de la GPU
  • La GPU queda inactiva entre bloques del búfer de audio, por lo que la carga promedio puede parecer de, por ejemplo, solo 60%
    • No se sabe cuál es la heurística real de macOS, pero se supone que podría parecerse a un load average
    • Esa carga puede no superar el umbral necesario para que suba el reloj de la GPU
  • Anukari necesita baja latencia para cumplir sus restricciones de tiempo real, y para eso necesita un reloj de GPU alto
  • No se sabe qué tan bajo puede caer el reloj de la GPU de Apple, pero puede bajar lo suficiente como para volver inutilizable a Anukari

El problema del reloj confirmado con el profiler de Metal

  • Con el profiler de Metal de Apple Instruments incluido en Xcode confirmaron que Anukari está limitado por ALU
  • El profiler de Metal permite elegir el “Performance State” de Metal durante el perfilado
    • Esta configuración no puede ajustarse fuera del profiler
  • Con performance state en Maximum, Anukari funciona perfectamente
  • Con performance state en Minimum, el funcionamiento empeora mucho
  • La diferencia entre ambos estados deja claro que la velocidad del reloj de la GPU es el factor central del problema de rendimiento de Anukari

El workaround de “waste makes haste” y sus límites

  • Como macOS no sube el reloj de la GPU cuando hace falta, Anukari usa un workaround aparte
  • Ejecuta una segunda tarea de GPU en paralelo con el trabajo de cálculo de audio para crear una carga promedio alta y empujar a macOS a subir el reloj
    • Esta tarea está ajustada para usar la menor cantidad posible de recursos de GPU y aun así activar la heurística del reloj
    • En la práctica es un spin loop para calentar la GPU
  • A esta estrategia la llaman “waste makes haste” y está documentada en detalle en un devlog relacionado
  • En la MacBook M1 del desarrollador, este método resolvió completamente el problema y Anukari corre de forma estable
  • Pero después del lanzamiento de Anukari Beta aparecieron problemas en algunos usuarios de macOS
    • En particular, parece haber muchos problemas de rendimiento en usuarios con hardware Apple Pro o Max
    • El artículo plantea como hipótesis la posibilidad de relojes independientes por chiplet de GPU, o que la carga del spin sea demasiado conservadora en GPUs más potentes

Qué solución le pide a Apple

  • Partiendo de que los ingenieros de Apple probablemente saben más del tema, propone algunas soluciones posibles
  • Solution 1: Extender el concepto de Audio Workgroup al procesamiento en GPU
    • El procesamiento de audio en macOS se realiza en un hilo o grupo de hilos llamado Audio Workgroup
    • El sistema operativo entiende que esos hilos tienen restricciones de tiempo real y les da prioridad
    • Podría tratar la MTLCommandQueue administrada por esos hilos de Audio Workgroup como trabajo en tiempo real y ajustar el reloj de la GPU en consecuencia
  • Solution 2: Agregar al API de Metal una opción para marcar sensibilidad de tiempo real en MTLCommandQueue
    • Eso permitiría ajustar el reloj del chiplet de GPU que procesa esa queue según esa necesidad
  • Solution 3: Si ya existe una forma de obtener este comportamiento, bastaría con que Apple la indique
  • Al inicio del artículo se agrega que Apple ya se puso en contacto, y que hay más detalles en otro texto

Comparación con Game Mode y Windows

  • El Game Mode de Apple parece parecido a lo que Anukari necesita, pero es difícil de aplicar
    • Game Mode funciona a nivel de proceso
    • Anukari normalmente se usa como plugin dentro de otro proceso, y ese proceso no soporta Game Mode
    • Anukari no puede controlarlo directamente
    • Game Mode además exige fullscreen, y Anukari normalmente no está en fullscreen
  • En Windows este problema no ocurre
    • No está claro si es porque Windows le da más control al usuario sobre el estado de rendimiento, o porque el driver de NVIDIA es menos conservador con el consumo energético
    • En Windows no hace falta el spin loop
  • Compara que PCs con Windows y GPU modestas pueden ejecutar bien Anukari, mientras que una costosa Mac M4 Max puede tener stutter

Por qué el pipelining no encaja

  • Pipelinear el código de GPU para saturarla funciona bien en trabajos orientados a throughput, pero Anukari es una tarea sensible a la latencia
  • Si se programan por adelantado varios kernels de simulación física, la CPU podría preparar el siguiente bloque mientras la GPU procesa el bloque de audio actual
  • Pero el pipelining aumenta el throughput a costa de elevar la latencia
  • Cada ejecución del kernel de Anukari necesita acceder a datos de entrada de audio en tiempo real, como la entrada de micrófono
  • No se puede usar ejecución especulativa para procesar por adelantado el siguiente bloque de audio porque todavía no existen los datos de entrada necesarios

El problema de poner el spin kernel en la misma MTLCommandQueue

  • Si la causa real fuera que el spin kernel y el physics kernel corren en chiplets distintos de la GPU, ponerlos en la misma MTLCommandQueue podría parecer la solución
  • De hecho, se probó ese método, pero no funcionó
  • La razón es que Anukari es una tarea sensible a la latencia
    • El spin kernel a veces tarda un poco más de lo esperado
    • Ese tiempo se mete en el tiempo de ejecución del physics kernel
  • También se experimentó con un spin kernel pequeño y memoria unificada volatile para que la CPU escribiera una bandera de “exit kernel early”
  • Incluso con esos mecanismos, hubo casos en los que el spin kernel invadía el tiempo del physics kernel

Por qué es difícil hacer GPU kernel hedging

  • También se consideró una estrategia tipo request hedging de sistemas distribuidos: ejecutar varias copias del kernel físico y usar el resultado del que termine primero
  • Esto podría reducir la latencia de cola y su variabilidad, y al mismo tiempo crear carga de GPU para que el sistema operativo eleve el estado de rendimiento
  • Pero en Anukari trae varios problemas
    • Si un physics kernel tarda más que el ciclo de un bloque de audio, ese flujo de kernels se atrasa
    • Ese flujo atrasado tendría que ponerse al día en bloques futuros, lo que requiere un fast-forward copiando el estado interno de otro flujo
  • Copiar el estado interno es costoso
    • El estado interno más grande es el búfer de audio usado para las delay lines
    • Guarda 1 segundo de audio pasado por cada micrófono
    • Su tamaño es 48,000 samples * 50 mics * 2 channels * 16 voices * 4 bytes, es decir 307MB
    • Con sample rates más altos, es todavía mayor
  • Para hacerlo de forma eficiente, habría que rastrear con precisión las regiones dirty de cada flujo de kernel con hedging y copiar solo esas partes
    • Pero la disposición de memoria del búfer está optimizada para la carga de lectura del physics kernel
    • Incluso copiando lo mínimo, habría que mover regiones dispersas por todo el búfer, lo que lo vuelve lento
  • Los cambios del modelo hechos por el usuario también tendrían que propagarse a todos los kernels con hedging
  • El physics kernel tiene una huella de GPU mucho mayor que el spin kernel de “waste makes haste”
    • El hedging generaría más carga innecesaria en la GPU y podría reducir la cantidad de instancias de Anukari que pueden ejecutarse en paralelo
    • Incluso los propios hedge kernels podrían competir entre sí y hacer que todos se vuelvan más lentos

Las optimizaciones ya realizadas y por qué la GPU es indispensable

  • La simulación de Anukari está limitada por ALU, así que no queda mucho margen para optimizaciones típicas como mejorar patrones de acceso a memoria
  • Para subir el rendimiento hay que optimizar el throughput aritmético
    • Donde es posible se usan operaciones FP16 para saturar mejor las ALU de Apple
    • Se ajusta el orden de instrucciones con micro-benchmarks
    • Todo el estado físico se mantiene en memoria L1
    • Se reordena el orden de carga para favorecer la vectorización
  • También aprovechan que los hilos de un SIMD-group de Apple suelen compartir instruction pointer
    • Distintos objetos físicos pueden divergir mucho en sus ramas
    • Si dentro de un mismo SIMD-group se simulan dos tipos de objeto, el instruction masking los vuelve más lentos
    • Para evitarlo, se optimiza dinámicamente la disposición en memoria de los objetos físicos para reducir la cantidad de tipos de objeto ejecutados dentro de un SIMD-group
    • Esta optimización está explicada en detalle en the new warp alignment optimizer
  • Puede haber más margen para optimización aritmética, pero estiman que solo daría mejoras de un dígito en puntos porcentuales
  • En máquinas potentes, Anukari puede simular entre 768 y 1024 objetos físicos
    • Cada objeto puede conectarse arbitrariamente con otros
    • Los objetos normalmente realizan implicit Euler integration a una sample rate de audio de 48,000 muestras por segundo
    • Cada objeto tiene entre 3 y 10 parámetros de comportamiento
    • Algunas operaciones incluyen cálculos costosos como rotación de vectores, exp() y log()
    • Para polyphony, toda la simulación física se ejecuta hasta en 16 copias paralelas
  • Este enfoque no fue viable en CPU; hacen falta las muchas ALU de la GPU, el control sobre la disposición de caché L1 y estructuras de concurrencia como threadgroup_barrier
  • Anukari no puede existir sin procesamiento en GPU

Por qué la API de GPU Audio no es la solución

  • El CEO de GPU Audio, Alexander Talashov, ha dicho que quizá el problema podría resolverse si Anukari usara la API de GPU Audio
  • El desarrollador considera GPU Audio un buen producto, que hace accesible la GPU para DSP
  • Pero concluye que en Anukari no sería útil
  • A diferencia de una aplicación DSP tradicional, Anukari se parece más a un integrador de ecuaciones diferenciales numéricas
    • Sí tiene algo de DSP, pero la mayor parte del cálculo es integración euleriana
    • El DSP del mundo físico, como la compresión de micrófonos, se procesa inline dentro del cálculo físico de la GPU
  • Anukari programa la GPU directamente en capas bajas de Metal
  • Lo que hace falta es que Apple eleve de forma confiable la velocidad del reloj de la GPU

1 comentarios

 
GN⁺ 2025-05-07
Opiniones de Hacker News
  • Quizá algunos hayan visto Anukari en mi publicación de Show HN: https://news.ycombinator.com/item?id=43873074

    En ese hilo surgió el tema del rendimiento en macOS. Anukari funciona bien en la mayoría de los Apple silicon, incluido el M1 básico, y todas mis pruebas las hice en un M1 básico, con resultados excelentes. El hardware es realmente impresionante.

    Pero para que funcionara, tuve que implementar una solución alternativa extraña que hace que macOS suba la velocidad de reloj de la GPU para que el procesamiento de audio sea lo suficientemente rápido. Las heurísticas normales con las que macOS decide el estado de rendimiento de la GPU no entienden la carga de trabajo particular de Anukari.

    Así que terminé documentando toda la situación con demasiado detalle, y quería pedir ayuda para que me conectaran con la persona adecuada en Apple, quizá alguien responsable del API de Metal. Ayuda, por favor :)

    • Dijiste que era “un artículo muy largo y muy técnico”, pero al leerlo hasta el final no me pareció demasiado largo, y sí muy claro y bien escrito; además fue útil. Muy bien escrito.

      Nunca he tenido una Mac y mi PC también es viejo, así que no tiene una GPU decente; por eso es poco probable que pueda probar Anukari pronto, pero se ve realmente genial y me da pena. Espero que se resuelva pronto.

    • Me pregunto si ya probaste este entitlement: https://developer.apple.com/documentation/bundleresources/en...

      Me pregunto si com.apple.developer.sustained-execution también funciona en el sentido contrario.

    • Es un artículo interesante y el problema también es interesante. Creo que la razón por la que falla la idea de ejecutar trabajo en la misma cola termina siendo la misma que el problema original. Por la velocidad de reloj variable, la planificación precisa se vuelve imposible, y según cómo el sistema operativo haya fijado el reloj de la GPU, el momento en que se detiene el spin se desfasa respecto del momento ideal y se produce aliasing.

      Si es así, puede que el trabajo de spin no sea lo suficientemente complejo como para llevar la GPU al reloj máximo. Si realmente estuviera funcionando al máximo rendimiento, debería ser posible ajustar de forma estable el momento de finalización del spin sin agregar un PLL por software. No vi una explicación detallada de cómo está implementado el spin, pero un bucle de spin más fiel, que mantenga bajo carga constante más partes de la GPU, parecería más eficaz para mantener el reloj en el máximo rendimiento.

    • Me perdí el Show HN, pero en cuanto lo vi pensé que encajaría muy bien con paisajes sonoros ASMR creativos y audio multidimensional inmersivo. Sería genial que tú o alguno de los usuarios hiciera una demo. Felicitaciones por el proyecto y ojalá recibas ayuda con el tema de Apple.

    • El artículo estuvo bueno y la explicación fue clara, así que fue fácil de entender. Definitivamente he experimentado en otros contextos problemas como los que describiste.

  • Gente, funcionó. ¡Tuve una conversación muy productiva con la persona exacta del equipo de Metal! Gracias por ayudar a llamar la atención de Apple. No esperaba para nada tanto apoyo.

    https://anukari.com/blog/devlog/productive-conversation-appl...

    • Es bueno que ahora haya una solución alternativa, pero el hecho de que ni siquiera puedas compartir cuál es esa solución, irónicamente, ilustra a la perfección la última frase de https://news.ycombinator.com/item?id=43904921 sobre la forma en que Apple se comunica.

      Es algo como: “Si configuras este valor así y luego lo cambias de esta otra forma, funciona. No está documentado, pero ahora ya lo sabes”.

      Al implementar la solución alternativa, sería bueno que pudieras ponerla en una función con un nombre muy explícito, para que otras personas que tengan restricciones de GPU sensibles a la latencia similares puedan encontrar alguna pista del conjuro mágico, aunque sea mediante desensamblado.

    • HN volvió a cumplir una vez más su propósito original: atravesar las barreras burocráticas frente al soporte al cliente de una gran empresa.

      Felicitaciones por el proyecto y mucha suerte.

  • Trabajé en dos empresas conocidas que tenían apps muy famosas en la Apple App Store.

    A los equipos de Apple con los que hablábamos no les interesaban en absoluto nuestros problemas; en cambio, nos invitaban seguido a sus oficinas para hablar de las funciones más recientes que iban a presentar en la WWDC, prácticamente obligándonos a darles soporte. Ese fue el principio y el fin de nuestra relación con ellos. Si queríamos averiguar por qué el software lleno de bugs de Apple no funcionaba, teníamos que abrir un ticket de soporte técnico.

    La gente de relaciones con desarrolladores de Apple no es gente seria.

    • Como muestra el autor del post original más arriba, me alegra que mi experiencia no sea una regla general. Pero hace unos 10 años, cuando trabajaba en una empresa con una app bastante conocida, una actualización destruyó por completo el rendimiento de la app.

      Exactamente al mismo tiempo, un competidor lanzó una app sin problemas de rendimiento. Resultó que el desarrollador de esa app competidora era alguien que había dejado Apple hacía poco, y había dejado una trampa no documentada en el driver de video de Apple que rompía nuestra app. Solo pudimos arreglarla desensamblando el binario de la competencia para encontrar el cambio no documentado. Ese desarrollador incluso le escribió un correo burlándose a nuestro CEO. Qué mundo tan maravilloso.

  • El profiler de Metal tiene una función muy útil que permite elegir el estado de rendimiento de Metal mientras se perfila una aplicación. Fuera del profiler no se puede configurar.

    Eso hace pensar que debe haber un API privado. ¿No sería más fácil ir por el camino de la ingeniería inversa? Claro, siempre que no termine requiriendo privilegios especiales imposibles de sortear sin desactivar SIP.

    • Esto necesariamente tiene que ser un API privado. El artículo también dice esto:

      “El profiler de Metal tiene una función muy útil que permite elegir el ‘Performance State’ de Metal mientras se perfila una aplicación. Fuera del profiler no se puede configurar”.

      Si no fuera un API privado, ¿cómo podría hacerlo el profiler de Metal? ¿No se podría observar el profiler con alguna herramienta de depuración para averiguar qué ocurre internamente?

  • El problema de abrir esta API es que demasiados desarrolladores van a dejar forzado siempre el modo de máximo rendimiento. No sé si realmente haya una buena forma de impedirlo al ofrecer la API.

    • En dispositivos con batería, ya existen infinitas formas de que una app desperdicie energía. Al final, el sistema ya se basa en confiar en que los desarrolladores no ejecuten, deliberadamente o por accidente, tareas intensivas en energía de manera innecesaria. Que haya una API más que pueda desperdiciar energía si no se usa bien no cambiaría mucho.

    • El artículo también habla de Modo Juego, una función de los sistemas operativos recientes de Apple optimizada para estos casos. Cuando se activa Modo Juego aparece una notificación, y la mayoría de las aplicaciones no querrían eso. Hasta ahora no he visto casos de abuso de esto.

    • Los desarrolladores todavía no están abusando de los grupos de trabajo de audio en todos los pools de hilos para conseguir planificación en P-core y alta prioridad. Si es así, eso sugiere que, cuando un grupo de trabajo de audio emite comandos a la GPU, se podría aplicar una especie de timeout al downclock de la GPU con base en la última vez que el grupo de trabajo envió datos.

      El audio en GPU hoy es un campo muy de nicho, pero la empresa mencionada en el texto publicó recientemente un SDK, así que podría volverse más popular. Aun así, no me convence. Procesarlo en la GPU casi equivale a decir que no te importa la latencia, así que creo que bastaría con aumentar el tamaño del búfer de entrada/salida.

    • Incluso si se abusara de la API, sería más eficiente que ejecutar trabajo falso ocupado para lograr lo mismo. Las apps ya pueden hacerlo sin la API, o sin los permisos que la API podría exigir.

    • ¿Y si se concediera permiso manualmente? Aunque lo escondan en algún lado, es muy probable que algunas apps muy de nicho lo necesiten.

      Y a nivel de sistema operativo, Zoom, Teams y los navegadores web pueden quedar denegados por defecto :)

  • La mejor forma de hacer esto:

    1. Revisar videos de WWDC y encontrar al ingeniero que parezca entender mejor el problema al que te estás enfrentando.

    2. Si es Michael Thomson, mandarle un email directo con un formato tipo mthomson@apple.com.

    • O, si no, escribirle a su hermano Pichael a pthomson.
  • Como nota aparte, estaría bueno que Anukari sacara un paquete de sonidos de Mick Gordon y compartiera las ganancias con él. Ese tipo está haciendo cosas de verdad dementes, y la demo también es increíble. Si ya existe una herramienta tan potente, colaborar con artistas es buen negocio y también algo bueno para el mundo. Si te gusta Mick Gordon, claro; a mí sí.

  • No necesito para nada esta app, pero es realmente genial. Apps como esta le devuelven la diversión a la computación. No quiero decir que ahora no haya nada divertido, sino que me recuerda a épocas pasadas en las que circulaban programas más gráficos y experimentales, incluso la demoscene.

  • No hay que perderse el enlace https://x.com/Mick_Gordon/status/1918146487948919222 del penúltimo párrafo. Es una demo hecha por Mick Gordon, y @anukarimusic respondió:

    “jaja, es el segundo día desde el lanzamiento y ya destruiste por completo todas las demos que hice usándolo a diario durante 2 años”

  • Actualizar 1024 objetos a 48 kHz parece posible incluso en CPU, según cómo esté escrito el código. ¿No son 48 millones de actualizaciones por segundo? Parece adecuado para paralelizar algunos bucles entre núcleos con OpenMP.

      1. Para la polifonía, Anukari ejecuta hasta 16 copias completas del modelo físico. Es decir, 16 * 1024 * 48K. Tendré que actualizar la entrada del blog.

      2. Como el usuario puede conectar los objetos arbitrariamente entre sí, cada objeto tiene que leer y procesar conexiones a N otras entidades.

      3. Para usar toda la CPU, se necesita sincronización entre núcleos en cada paso físico, y eso es lento.

      4. La carga de procesamiento por objeto es bastante grande. Hay muchas funciones trascendentales, aunque se pueden aproximar, y también hay muchas funciones en sí. Todos los parámetros pueden modularse, debe ser seguro ante NaN, etc.

      5. Los usuarios quieren ejecutar varias instancias de Anukari en paralelo para varias pistas, efectos y demás.

      Visto de otra manera, es 4 GHz / (16 voice * 1024 obj * 4 connections * 48,000 sample) = 1.3 cycles per thing.

      La GPU procesa esta carga de trabajo en un instante. Es una arquitectura que encaja perfectamente. Puede procesar 16 voice * 1024 obj de forma totalmente paralela, la sincronización de cada paso es sencilla y el usuario puede administrar la caché L1.

    • Si el cálculo es correcto, da 83 ciclos de reloj para calcular una muestra. Con 16 núcleos, teóricamente serían 1333 ciclos, que no es tanto; más aún considerando que no se puede usar la CPU casi al 100% todo el tiempo.