El SDK neutrón es una herramienta para los investigadores que les permite envolver sus propios modelos de audio y ejecutarlos en un DAW usando nuestro complemento Neutron. Ofrecemos funcionalidad para cargar los modelos localmente en el complemento y contribuirlos a la lista predeterminada de modelos que están disponibles para cualquier persona que ejecute el complemento. Esperamos que esto permita a los investigadores probar fácilmente sus modelos en un DAW, pero también proporcionar a los creadores una colección de modelos interesantes.
Juce es el estándar de la industria para construir complementos de audio. Debido a esto, se necesita el conocimiento de C ++ para poder construir complementos de audio muy simples. Sin embargo, es raro que los investigadores de audio de IA tengan una amplia experiencia con C ++ y puedan construir dicho complemento. Además, es una inversión de tiempo grave que podría dedicarse al desarrollo de mejores algoritmos. Neutrone hace posible construir modelos utilizando herramientas familiares como Pytorch y con una cantidad mínima de código de Python Wrap estos modelos de modo que puedan ser ejecutados por el complemento Neutron. Conseguir un modelo en funcionamiento dentro de un DAW se puede hacer en menos de un día sin necesidad de código o conocimiento C ++.
El SDK proporciona soporte para el almacenamiento automático de entradas y salidas a su modelo y la frecuencia de muestreo sobre la marcha y la conversión estéreo-mono. Permite un modelo que solo se puede ejecutar con un número predefinido de muestras que se utilizarán en el DAW a cualquier velocidad de muestreo y cualquier tamaño de búfer sin problemas. Además, dentro de las herramientas SDK para la evaluación comparativa y el perfil están fácilmente disponibles para que pueda depurar y probar fácilmente el rendimiento de sus modelos.
Puede instalar neutone_sdk usando PIP:
pip install neutone_sdk
El complemento Neutron está disponible en https://neutone.space. Actualmente ofrecemos complementos VST3 y AU que se pueden usar para cargar los modelos creados con este SDK. Visite el sitio web para obtener más información.
Si solo desea envolver un modelo sin pasar por una descripción detallada de lo que todo hace, preparamos estos ejemplos para usted.
El SDK proporciona funcionalidad para envolver los modelos Pytorch existentes de una manera que pueda hacerlos ejecutables dentro del complemento VST. En esencia, el complemento envía trozos de muestras de audio a una cierta frecuencia de muestreo como entrada y espera la misma cantidad de muestras en la salida. El usuario del SDK puede especificar qué velocidad (s) y tamaño (s) de búfer (s) de sus modelos funcionan de manera óptima. El SDK luego garantiza que el pase de avance del modelo recibirá audio en una de estas combinaciones (sample_rate, buffer_size). Hay cuatro perillas disponibles que permiten a los usuarios del complemento alimentar parámetros adicionales al modelo en tiempo de ejecución. Se pueden habilitar o deshabilitar según sea necesario a través del SDK.
Usando la función de exportación incluida, se ejecuta automáticamente una serie de pruebas para garantizar que los modelos se comporten como se esperaba y están listos para ser cargados por el complemento.
Las herramientas de Benchmarking and Gerriling CLI están disponibles para una mayor depuración y prueba de modelos envueltos. Es posible comparar la velocidad y la latencia de un modelo en una gama de combinaciones simuladas de DAW comunes (sample_rate, buffere_size), así como perfilar la memoria y el uso de la CPU.
Proporcionamos varios modelos en el directorio de ejemplos. Pasaremos por uno de los modelos más simples, un modelo de distorsión, para ilustrar.
Suponga que tenemos el siguiente modelo Pytorch. Los parámetros se cubrirán más adelante, nos centraremos en las entradas y salidas por ahora. Suponga que este modelo recibe un tensor de forma (2, buffer_size) como una entrada donde buffer_size es un parámetro que se puede especificar.
class ClipperModel ( nn . Module ):
def forward ( self , x : Tensor , min_val : float , max_val : float , gain : float ) -> Tensor :
return torch . clip ( x , min = min_val * gain , max = max_val * gain )Para ejecutar esto dentro del VST, el envoltorio más simple que podemos escribir es subclasificando el WaveFormToWaveFormBase baseclass.
class ClipperModelWrapper ( WaveformToWaveformBase ):
@ torch . jit . export
def is_input_mono ( self ) -> bool :
return False
@ torch . jit . export
def is_output_mono ( self ) -> bool :
return False
@ torch . jit . export
def get_native_sample_rates ( self ) -> List [ int ]:
return [] # Supports all sample rates
@ torch . jit . export
def get_native_buffer_sizes ( self ) -> List [ int ]:
return [] # Supports all buffer sizes
def do_forward_pass ( self , x : Tensor , params : Dict [ str , Tensor ]) -> Tensor :
# ... Parameter unwrap logic
x = self . model . forward ( x , min_val , max_val , gain )
return x El método que hace la mayor parte del trabajo es do_forward_pass . En este caso, es solo un paso simple, pero lo usaremos para manejar los parámetros más adelante.
Por defecto, el VST se ejecuta como stereo-stereo pero cuando se desea MONO para el modelo, podemos usar is_input_mono e is_output_mono para informar el SDK y hacer que las entradas y salidas se conviertan automáticamente. Si is_input_mono se alterna un tensor en forma de promedio (1, buffer_size) se pasará como una entrada en lugar de (2, buffer_size) . Si is_output_mono se alterna, se espera que do_forward_pass devuelva un tensor mono (forma (1, buffer_size) ) que luego se duplicará a través de ambos canales en la salida del VST. Esto se hace dentro del SDK para evitar asignaciones de memoria innecesaria durante cada pase.
get_native_sample_rates y get_native_buffer_sizes se puede usar para especificar cualquier tasa de muestra o tamaños de búfer. En la mayoría de los casos, se espera que solo tengan un elemento, pero se proporciona una flexibilidad adicional para modelos más complejos. En caso de que se proporcionen múltiples opciones, el SDK intentará encontrar el mejor para la configuración actual del DAW. Siempre que el tamaño de la frecuencia de muestreo o el tamaño del búfer es diferente de la de la envoltura DAW, se activa automáticamente que se convierte en la velocidad de muestreo correcta o implementa una cola FIFO para el tamaño del búfer solicitado o ambos. Esto incurrirá en una pequeña multa de rendimiento y agregará cierta cantidad de retraso. En caso de que un modelo sea compatible con cualquier frecuencia de muestreo y/o tamaño del búfer, estas listas se pueden dejar vacías.
Esto significa que el tensor x en el método do_forward_pass está garantizado que es de forma (1 if is_input_mono else 2, buffer_size) donde se eligirá buffer_size en el tiempo de ejecución de la lista proporcionada en el método get_native_buffer_sizes . El tensor x también estará en una de las tasas de muestreo de la lista proporcionada en el método get_native_sample_rates .
Proporcionamos una función de ayudante save_neutone_model que guarda modelos en el disco. Por defecto, esto convertirá los modelos en TorchScript y los ejecutará a través de una serie de verificaciones para garantizar que el complemento pueda cargarlos. El archivo model.nm resultante se puede cargar dentro del complemento utilizando el botón load your own . Lea a continuación cómo enviar modelos a la colección predeterminada visible para todos utilizando el complemento.
Para los modelos que pueden usar señales de acondicionamiento, actualmente proporcionamos cuatro parámetros de perilla configurables. Dentro del ClipperModelWrapper definido anteriormente, podemos incluir lo siguiente:
class ClipperModelWrapper ( WaveformToWaveformBase ):
...
def get_neutone_parameters ( self ) -> List [ NeutoneParameter ]:
return [ NeutoneParameter ( name = "min" , description = "min clip threshold" , default_value = 0.5 ),
NeutoneParameter ( name = "max" , description = "max clip threshold" , default_value = 1.0 ),
NeutoneParameter ( name = "gain" , description = "scale clip threshold" , default_value = 1.0 )]
def do_forward_pass ( self , x : Tensor , params : Dict [ str , Tensor ]) -> Tensor :
min_val , max_val , gain = params [ "min" ], params [ "max" ], params [ "gain" ]
x = self . model . forward ( x , min_val , max_val , gain )
return x Durante el avance de la variable params será un diccionario como el siguiente:
{
"min" : torch . Tensor ([ 0.5 ] * buffer_size ),
"max" : torch . Tensor ([ 1.0 ] * buffer_size ),
"gain" : torch . Tensor ([ 1.0 ] * buffer_size )
} Las claves del diccionario se especifican en la función get_parameters .
Los parámetros siempre tomarán valores entre 0 y 1 y la función do_forward_pass se puede utilizar para hacer cualquier reescalado necesario antes de ejecutar el método interno de avance del modelo.
Además, los parámetros enviados por el complemento entran en una granularidad de nivel de muestra. Por defecto, tomamos la media de cada búfer y devolvemos un solo flotador (como tensor), pero el método aggregate_param se puede usar para anular el método de agregación. Vea el archivo de exportación completo de Clipper para un ejemplo de preservación de esta granularidad.
Algunos modelos de audio retrasarán el audio para una cierta cantidad de muestras. Esto depende de la arquitectura de cada modelo en particular. Para que la señal húmeda y seca que pase por el complemento esté alineada, los usuarios deben informar cuántas muestras de retraso inducen su modelo. Los calc_model_delay_samples se pueden usar para especificar el número de muestras de retraso. Los modelos RAVE en promedio tienen un búfer de retraso (2048 muestras) que se comunica estáticamente en el método calc_model_delay_samples y se puede ver en los ejemplos. Los modelos implementados con Overlap-Add tendrán un retraso igual al número de muestras utilizadas para el paseos cruzados como se ve en el envoltorio de modelo DemUCS o el ejemplo del filtro espectral.
Calcular el retraso que agrega su modelo puede ser difícil, especialmente porque puede haber múltiples fuentes diferentes de retraso que deben combinarse (por ejemplo, retraso en cosecha, retraso del filtro, retraso del búfer de la cabeza de búsqueda y / o redes neuronales entrenadas en audio seco y húmedo inalineados) . Vale la pena pasar un tiempo extra probando el modelo en su DAW para asegurarse de que el retraso se reporta correctamente.
Agregar un búfer de aspecto a su modelo puede ser útil para los modelos que requieren una cierta cantidad de contexto adicional para obtener resultados útiles. Se puede habilitar fácilmente un búfer de apariencia indicando cuántas muestras de lookbehind necesita en el método get_look_behind_samples . Cuando este método devuelve un número mayor que cero, el método do_forward_pass siempre recibirá un tensor de forma (in_n_ch, look_behind_samples + buffer_size) , pero aún debe devolver un tensor de forma (out_n_ch, buffer_size) de las últimas muestras.
Recomendamos evitar el uso de un búfer de apariencia cuando sea posible, ya que hace que su modelo sea menos eficiente y puede dar lugar a cálculos desperdiciados durante cada pase hacia adelante. Si usa un modelo puramente convolucional, intente cambiar todas las convoluciones a convoluciones en caché.
Es común que los modelos de IA actúen de manera inesperada cuando se presente con entradas fuera de las presentes en su distribución de capacitación. Proporcionamos una serie de filtros comunes (bajo bajo, pase alto, pase de banda, parada de banda) en el archivo neutone_sdk/filters.py. Estos se pueden usar durante el pase hacia adelante para restringir el dominio de las entradas que entran en el modelo. Algunos de ellos pueden inducir una pequeña cantidad de retraso, consulte el archivo ejemplos/ejemplo_clipper_prefilter.py para obtener un ejemplo simple sobre cómo configurar un filtro.
El complemento contiene una lista predeterminada de modelos dirigidos a creadores que desean usarlos durante su proceso creativo. Alentamos a los usuarios a enviar sus modelos una vez que estén contentos con los resultados que obtienen para que la comunidad pueda usarlos en general. Para la presentación requerimos algunos metadatos adicionales que se utilizarán para mostrar información sobre el modelo dirigido tanto a los creadores como a otros investigadores. Esto se mostrará tanto en el sitio web de Neutone como dentro del complemento.
Saltando el modelo de Clipper anterior, aquí hay un ejemplo más realista basado en un modelo de sobremarcha TCN aleatorio inspirado en Micro-TCN.
class OverdriveModelWrapper ( WaveformToWaveformBase ):
def get_model_name ( self ) -> str :
return "conv1d-overdrive.random"
def get_model_authors ( self ) -> List [ str ]:
return [ "Nao Tokui" ]
def get_model_short_description ( self ) -> str :
return "Neural distortion/overdrive effect"
def get_model_long_description ( self ) -> str :
return "Neural distortion/overdrive effect through randomly initialized Convolutional Neural Network"
def get_technical_description ( self ) -> str :
return "Random distortion/overdrive effect through randomly initialized Temporal-1D-convolution layers. You'll get different types of distortion by re-initializing the weight or changing the activation function. Based on the idea proposed by Steinmetz et al."
def get_tags ( self ) -> List [ str ]:
return [ "distortion" , "overdrive" ]
def get_model_version ( self ) -> str :
return "1.0.0"
def is_experimental ( self ) -> bool :
return False
def get_technical_links ( self ) -> Dict [ str , str ]:
return {
"Paper" : "https://arxiv.org/abs/2010.04237" ,
"Code" : "https://github.com/csteinmetz1/ronn"
}
def get_citation ( self ) -> str :
return "Steinmetz, C. J., & Reiss, J. D. (2020). Randomized overdrive neural networks. arXiv preprint arXiv:2010.04237."Consulte la documentación de los métodos dentro de Core.py, así como el modelo de sobremarcha aleatorio en el sitio web y en el complemento para comprender dónde se mostrará cada campo.
Para enviar un modelo, abra un problema en el repositorio de GitHub. Actualmente necesitamos lo siguiente:
save_neutone_model model.nm El SDK proporciona tres herramientas CLI que se pueden usar para depurar y probar modelos envueltos.
Ejemplo:
$ python -m neutone_sdk.benchmark benchmark-speed --model_file model.nm
INFO:__main__:Running benchmark for buffer sizes (128, 256, 512, 1024, 2048) and sample rates (48000,). Outliers will be removed from the calculation of mean and std and displayed separately if existing.
INFO:__main__:Sample rate: 48000 | Buffer size: 128 | duration: 0.014±0.002 | 1/RTF: 5.520 | Outliers: [0.008]
INFO:__main__:Sample rate: 48000 | Buffer size: 256 | duration: 0.028±0.003 | 1/RTF: 5.817 | Outliers: []
INFO:__main__:Sample rate: 48000 | Buffer size: 512 | duration: 0.053±0.003 | 1/RTF: 6.024 | Outliers: []
INFO:__main__:Sample rate: 48000 | Buffer size: 1024 | duration: 0.106±0.000 | 1/RTF: 6.056 | Outliers: []
INFO:__main__:Sample rate: 48000 | Buffer size: 2048 | duration: 0.212±0.000 | 1/RTF: 6.035 | Outliers: [0.213]
Ejecutar el punto de referencia de velocidad ejecutará automáticamente entradas aleatorias a través del modelo a una frecuencia de muestreo de 48000 y tamaños de búfer de (128, 256, 512, 1024, 2048) e informará el tiempo promedio que se toma para ejecutar inferencia por un búfer. A partir de esto, se calcula el 1/RTF que representa cuánto más rápido que el modelo es el modelo. A medida que este número aumente, el modelo utilizará menos recursos dentro del DAW. Es necesario que este número sea mayor que 1 para que el modelo pueda ejecutarse en tiempo real en la máquina en el que se ejecuta el punto de referencia.
Las velocidades de muestra y los tamaños de búfer que se están probando, así como el número de veces que el punto de referencia se repite internamente para calcular los promedios y el número de subprocesos utilizados para el cálculo están disponibles como parámetros. Ejecute python -m neutone_sdk.benchmark benchmark-speed --help para obtener más información. Al especificar las tasas de muestreo personalizadas o los tamaños de amortiguación, cada individuo uno debe pasar al CLI por separado. Por ejemplo: --sample_rate 48000 --sample_rate 44100 --buffer_size 32 --buffer_size 64 .
Si bien el punto de referencia de velocidad debe ser rápido, ya que los modelos se requieren en general para ser en tiempo real, es posible quedarse atascado si el modelo es demasiado lento. Asegúrese de elegir un número apropiado de tasas de muestreo y tamaños de búfer para probar.
Ejemplo:
$ python -m neutone_sdk.benchmark benchmark-latency model.nm
INFO:__main__:Native buffer sizes: [2048], Native sample rates: [48000]
INFO:__main__:Model exports/ravemodel/model.nm has the following delays for each sample rate / buffer size combination (lowest delay first):
INFO:__main__:Sample rate: 48000 | Buffer size: 2048 | Total delay: 0 | (Buffering delay: 0 | Model delay: 0)
INFO:__main__:Sample rate: 48000 | Buffer size: 1024 | Total delay: 1024 | (Buffering delay: 1024 | Model delay: 0)
INFO:__main__:Sample rate: 48000 | Buffer size: 512 | Total delay: 1536 | (Buffering delay: 1536 | Model delay: 0)
INFO:__main__:Sample rate: 48000 | Buffer size: 256 | Total delay: 1792 | (Buffering delay: 1792 | Model delay: 0)
INFO:__main__:Sample rate: 44100 | Buffer size: 128 | Total delay: 1920 | (Buffering delay: 1920 | Model delay: 0)
INFO:__main__:Sample rate: 48000 | Buffer size: 128 | Total delay: 1920 | (Buffering delay: 1920 | Model delay: 0)
INFO:__main__:Sample rate: 44100 | Buffer size: 256 | Total delay: 2048 | (Buffering delay: 2048 | Model delay: 0)
INFO:__main__:Sample rate: 44100 | Buffer size: 512 | Total delay: 2048 | (Buffering delay: 2048 | Model delay: 0)
INFO:__main__:Sample rate: 44100 | Buffer size: 1024 | Total delay: 2048 | (Buffering delay: 2048 | Model delay: 0)
INFO:__main__:Sample rate: 44100 | Buffer size: 2048 | Total delay: 2048 | (Buffering delay: 2048 | Model delay: 0) Ejecutar el punto de referencia de velocidad calculará automáticamente la latencia del modelo en combinaciones de sample_rate=(44100, 48000) y buffer_size=(128, 256, 512, 1024, 2048) . Esto ofrece una visión general de lo que sucederá para la configuración común de DAW. El retraso total se divide en el retraso de la amortiguación y el retraso del modelo. El creador del modelo informa el retraso del modelo en el envoltorio del modelo como se explicó anteriormente. El retraso del almacenamiento en búfer se calcula automáticamente por el SDK teniendo en cuenta la combinación de (sample_rate, buffer_size) especificada por el envoltorio (los nativos) y la especificada por el DAW en tiempo de ejecución. Ejecutar el modelo en su combinación (sample_rate, buffer_size) incurrirá en retraso mínimo.
Similar al punto de referencia de velocidad anterior, las combinaciones probadas de (sample_rate, buffer_size) se pueden especificar de la CLI. Ejecute python -m neutone_sdk.benchmark benchmark-latency --help para obtener más información.
$ python -m neutone_sdk.benchmark profile --model_file exports/ravemodel/model.nm
INFO:__main__:Profiling model exports/ravemodel/model.nm at sample rate 48000 and buffer size 128
STAGE:2023-09-28 14:34:53 96328:4714960 ActivityProfilerController.cpp:311] Completed Stage: Warm Up
30it [00:00, 37.32it/s]
STAGE:2023-09-28 14:34:54 96328:4714960 ActivityProfilerController.cpp:317] Completed Stage: Collection
STAGE:2023-09-28 14:34:54 96328:4714960 ActivityProfilerController.cpp:321] Completed Stage: Post Processing
INFO:__main__:Displaying Total CPU Time
INFO:__main__:-------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------
Name Self CPU % Self CPU CPU total % CPU total CPU time avg CPU Mem Self CPU Mem # of Calls
-------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------
forward 98.54% 799.982ms 102.06% 828.603ms 26.729ms 0 b -918.17 Kb 31
aten::convolution 0.12% 963.000us 0.95% 7.739ms 175.886us 530.62 Kb -143.50 Kb 44
...
...
Full output removed from GitHub.
La herramienta de perfil ejecutará el modelo a una frecuencia de muestreo de 48000 y un tamaño de amortiguación de 128 bajo el perfilador de Pytorch y emitirá una serie de ideas, como el tiempo total de CPU, el uso total de la memoria de la CPU (por función) y el uso de la memoria de la CPU agrupada (por grupo de llamadas de función). Esto se puede usar para identificar cuellos de botella en su código de modelo (incluso dentro de la llamada de modelo dentro de la llamada do_forward_pass ).
Similar a la evaluación comparativa, se puede ejecutar a diferentes combinaciones de tamaños de muestreo y tamaños de amortiguación, así como diferentes números de hilos. Ejecute python -m neutone_sdk.benchmark profile --help para obtener más información.
Agradecemos cualquier contribución al SDK. Agregue los tipos siempre que sea posible y use el formateador black para su legibilidad.
La hoja de ruta actual es:
El proyecto Audacitorch fue una gran inspiración para el desarrollo del SDK. Compruébalo aquí