Como lo he compartido con anterioridad, activamente practico el selfhosting. Entre los servicios que alojo se encuentra Navidrome, el cual es un servicio para streamear tu librería de música. La ventaja principal de usar esto frente a un servicio convencional como Tidal o Deezer es el control total sobre los archivos de audio que podrán ser consumidos por los clientes que se conecten al servidor.
Otra consecuencia de alojar tu propio servicio independiente, es la ausencia de algoritmos de recomendación, lo cual es en mayor parte algo bueno. Pero hay una tarea en la que aquellas criaturas del demonio son excelentes y a su vez no invaden el espacio sagrado que es la decisión del usuario: hallar canciones similares entre sí.\
Modelar la percepción humana…
Y es que esa tarea en específico cae en la misma categoría de problema que la clasificación de fotos. Puedes pasarte un mes entero organizando tu galería por fechas, por eventos o por el criterio que tú quieras y desees; pero al final del día, a la hora de consultar la información, nada le gana a un espacio vectorial de -dimensiones. Cuando quieras ver la foto de ese cartel cagado que viste una vez en la carretera, buscar “señal de transito graffiteada” siempre será más rápido e intuitivo que scrollear por toda tu galería pensando en qué fecha fue o en qué viaje estabas cuando tomaste esa foto.
De manera similar, puedes pasarte años curando tu colección de música con metadata impecable, etiquetando cada canción con un género apropiado que englobe lo mejor posible tu percepcíon de ella; pero al final del día, cuando quieras encontrar canciones similares a Osos Maduros de Mi mochila huele a semen, las etiquetas de género jamás le van a hacer la misma justicia que un espacio vectorial de -dimensiones, especialmente para géneros experimentales (como lo son mismamente nuestros maigos de MMHAS).
Esto es así porque hasta hora, este método de clasificación ha sido nuestro mejor intento en la titánica tarea que es modelar la percepción humana.
…con un intel i5 del 2015
Lamentablemente, hasta hace poco no existía ninguna forma de explorar una colección local de audio que fuera accesible e igual de efectiva que las soluciones proveídas por los servicios de streaming. Eso hasta que surgió AudioMuse-AI (AM-AI), un proyecto que intenté hacer funcionar tres veces a lo largo de cuatro meses, en tres máquinas distintas, siendo el fracaso del último intento lo que me inspiró a ponerme a investigar si había una mejor alternativa. No la había.
Pero incluso si hubiera sido capaz de correr AM-AI, esta solución presentaba varios detalles con los que no necesariamente me sentía muy cómodo:
-
La única aceleración por hardware disponible es CUDA, lo cual no es algo que normalmente se tenga disponible en un servidor de bajo perfil. Esto no sería un problema si fuera posible efectuar el análisis desde otra máquina que no sea donde se ejecuta la API principal.
-
El worker y la API deben correr en la misma máquina: La parte del backend que analiza la colección de música debe correr en la misma máquina host que la API que expone el análisis para su consulta. Esto no era óptimo para mi caso de uso, pues significaba sobrecargar la CPU durante ese tiempo y, cuando tu base de usuarios es toda tu familia, el homelab es producción, no puedes simplemente decidir que vas a degradar el servicio por tus huevos.
-
La documentación. Hablaré con franqueza en este punto, leyendo la documentación del proyecto es claro que está casi en su totalidad, generado usando inteligencia artificial, y se nota en donde más le duele a la IA; en la discrepancia entre documentos, muy posiblemente ocasionada por la falta de contexto suficiente a la hora de crearlos.
Dichos esos tres puntos, es importante reconocerle a nuestro amigo NeptuneHub que fue quien puso la primera piedra en demostrar que algo así es posible de tener en el mundo del selfhosting. No solo eso, ha sido un promotor activo en la adopción (y creación) de los estándares necesarios para hacer que herramientas como lo son la suya y la que presentaré a continuación puedan si quiera existir. Esto es de aplaudirse y respetarse.
De tantos oídos, ¿cuál elegir?
El principio es muy sencillo: tomas un modelo pre-entrenado para clasificar
música y le pasas una pista de audio, este genera embeddings que son
almacenados en una base de datos especializada en vectores (como sqlite-vec).
Una vez que tengas suficientes canciones almacenadas en tu base de datos,
puedes realizar operaciones sobre sus respectivos vectores. Crucialmente:
puedes tomar todos los vecinos de un track dado, lo cual te otorga una lista
de… redoble de tambores canciones similares.
La primera decisión se torna rápidamente en ¿qué modelo elegir? Las opciones sobran: MusicFM, musicnn (usado por AM-AI), MERT, MERIT, MuQ. Tras evaluar resultados y leer un par de artículos interesantes, terminé decantándome por MuQ pues es la alternativa que mejor puntúa el benchmark MARBLE actualmente. Asimismo, fue el modelo que mejor le fue en mis pruebas subjetivas, las cuales consistieron en comparar tracks que yo personalmente sé que son similares y ver si el modelo lograba cachar dichas similitudes.
A partir de allí, construir el backend es sencillamente instalar la extensión
sqlite-vec y crear tres tablas: una
tabla de vectores de tracks, una de artistas y otra de la metadata como tal.
Posteriormente definir los distintos métodos necesarios para implementar las
extensiones de SonicSimilarity de OpenSubsonic como lo son obtener tracks
similares, el camino entre dos tracks, artistas similares, etc.
Para el lado del cliente es igual de sencillo, se extraen los embeddings de MuQ
y se envían a la API de abspitch y listo.
Evidentemente obvié muchos detalles técnicos que no vienen al caso, pero si quisiera destacar uno: la aceleración por hardware.
Absolute Pitch resuelve el diseño monolítico de AM-AI marcando una separación clara entre cliente y base de datos, lo cual permite delegar a una máquina externa la inferencia del modelo, sin embargo, esto sirve de poco si dicha inferencia no se puede acelerar por hardware.
Por defecto, MuQ se corre con PyTorch, el cual es un framework altamente
dependiente de CUDA (principalmente porque está enfocado en desarrollo, no
inferencia de modelos). Para tener una mayor variedad de opciones de
aceleración fue necesario exportar el modelo a ONNX, un
formato abierto que, a diferencia de PyTorch, ofrece una gran variedad de
opciones para acelerar la
inferencia con hardware. Exportar el model a onnx fue relativamente trivial,
pues ya existe un método dedicado esto llamado
torch.onnx.export().
¿Y qué tal lo hace?
El análisis inicial tomó días, lo cual no es una exageración. Tardándose en promedio 12 segundos por canción (incluso con la caché de inferencia), fue necesario dejar mi laptop toda la noche analizando la librería tres veces para digerir una colección de ~8000 canciones. Pero cuando el proceso terminó, los resultados hablaban por si mismos.
Apuesto que nunca te habías dado cuenta que Sin Ti Ni Documentación de Daniel, Me, Estás Matando se parece mucho a Rayito De Luna de Los Panchos. O quizás ya sabías que Cantata de puentes amarillos tiene la misma vibra que Credulidad, ambas del gran Spinetta, pero al chile yo no, y me alegra tener disponible una herramienta que permita encontrar estas conexiones entre la colección de manera trivial.
Otra característica que fue necesario implementar fue la creación de un “camino” entre dos canciones. Dadas dos canciones, se busca un camino de tracks intermedios que navegen el espacio vectorial de manera continua hasta llegar a la canción final. Se consiguen efectos muy, muy extraños que son difíciles de describir en palabras, especialmente cuando le pones tareas difíciles como relacionar cumbias con música electrónica o solicitudes de ese estilo.