-
Novedades de BNNS Graph
BNNS Graph Builder API ahora permite a los desarrolladores escribir gráficos de operaciones usando el lenguaje familiar Swift para generar rutinas de preprocesamiento y posprocesamiento, así como pequeños modelos de aprendizaje automático. BNNS compila gráficos antes de la ejecución y admite casos de uso en tiempo real y sensibles a la latencia, como el procesamiento de audio. En esta sesión, revisamos el ejemplo de bit-crusher del año pasado y simplificamos el componente Swift eliminando la dependencia de un archivo Python separado y, en su lugar, implementamos el efecto de audio completamente en Swift. La API BNNS Graph Builder también es adecuada para preprocesar datos de imágenes antes de pasar esos datos a un modelo de aprendizaje automático. La sesión también incluye una demostración de cómo recortar los pixeles transparentes de una imagen con un canal alfa.
Capítulos
- 0:00 - Introducción
- 3:12 - Resumen de BNNSGraph
- 6:15 - BNNSGraphBuilder
- 16:58 - Uso de BNNSGraphBuilder
Recursos
Videos relacionados
WWDC25
WWDC24
-
Buscar este video…
-
-
8:31 - Introduction to BNNSGraphBuilder
import Accelerate func demo() throws { let context = try BNNSGraph.makeContext { builder in let x = builder.argument(name: "x", dataType: Float.self, shape: [8]) let y = builder.argument(name: "y", dataType: Float.self, shape: [8]) let product = x * y let mean = product.mean(axes: [0], keepDimensions: true) // Prints "shape: [1] | stride: [1]". print("mean", mean) return [ product, mean] } var args = context.argumentNames().map { name in return context.tensor(argument: name, fillKnownDynamicShapes: false)! } // Output arguments args[0].allocate(as: Float.self, count: 8) args[1].allocate(as: Float.self, count: 1) // Input arguments args[2].allocate( initializingFrom: [1, 2, 3, 4, 5, 6, 7, 8] as [Float]) args[3].allocate( initializingFrom: [8, 7, 6, 5, 4, 3, 2, 1] as [Float]) try context.executeFunction(arguments: &args) // [8.0, 14.0, 18.0, 20.0, 20.0, 18.0, 14.0, 8.0] print(args[0].makeArray(of: Float.self)) // [15.0] print(args[1].makeArray(of: Float.self)) args.forEach { $0.deallocate() } } -
12:04 - Strong typing
// Performs `result = mask0 .< mask1 ? bases.pow(exponents) : 0 let context = try BNNSGraph.makeContext { builder in let mask0 = builder.argument(dataType: Float16.self, shape: [-1]) let mask1 = builder.argument(dataType: Float16.self, shape: [-1]) let bases = builder.argument(dataType: Float16.self, shape: [-1]) let exponents = builder.argument(dataType: Int32.self, shape: [-1]) // `mask` contains Boolean values. let mask = mask0 .< mask1 // Cast integer exponents to FP16. var result = bases.pow(y: exponents.cast(to: Float16.self)) result = result * mask.cast(to: Float16.self) return [result] } -
14:15 - Slicing
let srcImage = #imageLiteral(resourceName: "squirrel.jpeg").cgImage( forProposedRect: nil, context: nil, hints: nil)! var cgImageFormat = vImage_CGImageFormat( bitsPerComponent: 32, bitsPerPixel: 32 * 3, colorSpace: CGColorSpaceCreateDeviceRGB(), bitmapInfo: CGBitmapInfo(alpha: .none, component: .float, byteOrder: .order32Host))! let source = try vImage.PixelBuffer(cgImage: srcImage, cgImageFormat: &cgImageFormat, pixelFormat: vImage.InterleavedFx3.self) let cropSize = 640 let horizontalMargin = (source.width - cropSize) / 2 let verticalMargin = (source.height - cropSize) / 2 let destination = vImage.PixelBuffer(size: .init(width: cropSize, height: cropSize), pixelFormat: vImage.InterleavedFx3.self) let context = try BNNSGraph.makeContext { builder in let src = builder.argument(name: "source", dataType: Float.self, shape: [ -1, -1, 3]) let result = src [ BNNSGraph.Builder.SliceRange(startIndex: verticalMargin, endIndex: -verticalMargin), BNNSGraph.Builder.SliceRange(startIndex: horizontalMargin, endIndex: -horizontalMargin), BNNSGraph.Builder.SliceRange.fillAll ] return [result] } source.withBNNSTensor { src in destination.withBNNSTensor { dst in var args = [dst, src] print(src) print(dst) try! context.executeFunction(arguments: &args) } } let result = destination.makeCGImage(cgImageFormat: cgImageFormat) -
17:31 - Preprocessing by thresholding on mean
let srcImage = #imageLiteral(resourceName: "birds.jpeg").cgImage( forProposedRect: nil, context: nil, hints: nil)! var cgImageFormat = vImage_CGImageFormat( bitsPerComponent: 16, bitsPerPixel: 16, colorSpace: CGColorSpaceCreateDeviceGray(), bitmapInfo: CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder16Little.rawValue | CGBitmapInfo.floatComponents.rawValue | CGImageAlphaInfo.none.rawValue))! let source = try! vImage.PixelBuffer<vImage.Planar16F>(cgImage: srcImage, cgImageFormat: &cgImageFormat) let destination = vImage.PixelBuffer<vImage.Planar16F>(size: source.size) let context = try BNNSGraph.makeContext { builder in let src = builder.argument(name: "source", dataType: Float16.self, shape: [-1, -1, 1]) let mean = src.mean(axes: [0, 1], keepDimensions: false) let thresholded = src .> mean let result = thresholded.cast(to: Float16.self) return [result] } source.withBNNSTensor { src in destination.withBNNSTensor { dst in var args = [dst, src] try! context.executeFunction(arguments: &args) } } let result = destination.makeCGImage(cgImageFormat: cgImageFormat) -
19:04 - Postprocessing
func postProcess(result: BNNSTensor, k: Int) throws -> ([Float32], [Int32]) { let context = try BNNSGraph.makeContext { builder in let x = builder.argument(dataType: Float32.self, shape: [-1]) let softmax = x.softmax(axis: 1) let topk = softmax.topK(k, axis: 1, findLargest: true) return [topk.values, topk.indices] } let indices = context.allocateTensor(argument: context.argumentNames()[0], fillKnownDynamicShapes: false)! let values = context.allocateTensor(argument: context.argumentNames()[1], fillKnownDynamicShapes: false)! var arguments = [values, indices, result] try context.executeFunction(arguments: &arguments) return (values.makeArray(of: Float32.self), indices.makeArray(of: Int32.self)) } -
21:03 - Bitcrusher in PyTorch
import coremltools as ct from coremltools.converters.mil import Builder as mb from coremltools.converters.mil.mil import ( get_new_symbol ) import torch import torch.nn as nn import torch.nn.functional as F class BitcrusherModel(nn.Module): def __init__(self): super(BitcrusherModel, self).__init__() def forward(self, source, resolution, saturationGain, dryWet): # saturation destination = source * saturationGain destination = F.tanh(destination) # quantization destination = destination * resolution destination = torch.round(destination) destination = destination / resolution # mix destination = destination * dryWet destination = 1.0 - dryWet source = source * dryWet destination = destination + source return destination -
21:03 - Bitcrusher in Swift
typealias BITCRUSHER_PRECISION = Float16 let context = try! BNNSGraph.makeContext { builder in var source = builder.argument(name: "source", dataType: BITCRUSHER_PRECISION.self, shape: [sampleCount, 1, 1]) let resolution = builder.argument(name: "resolution", dataType: BITCRUSHER_PRECISION.self, shape: [1, 1, 1]) let saturationGain = builder.argument(name: "saturationGain", dataType: BITCRUSHER_PRECISION.self, shape: [1, 1, 1]) var dryWet = builder.argument(name: "dryWet", dataType: BITCRUSHER_PRECISION.self, shape: [1, 1, 1]) // saturation var destination = source * saturationGain destination = destination.tanh() // quantization destination = destination * resolution destination = destination.round() destination = destination / resolution // mix destination = destination * dryWet dryWet = BITCRUSHER_PRECISION(1) - dryWet source = source * dryWet destination = destination + source return [destination] } -
22:34 - Changing precision
typealias BITCRUSHER_PRECISION = Float16 let context = try! BNNSGraph.makeContext { builder in var source = builder.argument(name: "source", dataType: BITCRUSHER_PRECISION.self, shape: [sampleCount, 1, 1]) let resolution = builder.argument(name: "resolution", dataType: BITCRUSHER_PRECISION.self, shape: [1, 1, 1]) let saturationGain = builder.argument(name: "saturationGain", dataType: BITCRUSHER_PRECISION.self, shape: [1, 1, 1]) var dryWet = builder.argument(name: "dryWet", dataType: BITCRUSHER_PRECISION.self, shape: [1, 1, 1]) // saturation var destination = source * saturationGain destination = destination.tanh() // quantization destination = destination * resolution destination = destination.round() destination = destination / resolution // mix destination = destination * dryWet dryWet = BITCRUSHER_PRECISION(1) - dryWet source = source * dryWet destination = destination + source return [destination] }
-
-
- 0:00 - Introducción
El Grupo Vector y Numerics de Apple desarrolló BNNS, una biblioteca de aprendizaje automático para inferencia basada en CPU en apps, particularmente útil para el procesamiento de audio e imágenes en tiempo real. BNNSGraph, presentado el año pasado, mejoró la velocidad, la eficiencia y la facilidad de uso. Ahora, se agregó BNNSGraphBuilder, que permite la creación basada en Swift de pequeños modelos y gráficos para preprocesamiento y posprocesamiento. Esto se demuestra con el preprocesamiento de imágenes, el posprocesamiento de datos y la actualización de la muestra de la unidad de audio Bitcrusher.
- 3:12 - Resumen de BNNSGraph
BNNSGraph se recomienda para tareas de audio y baja latencia con plazos en tiempo real porque permite el control de la memoria y de múltiples subprocesos, mejorando el rendimiento en tiempo real. Anteriormente, BNNS requería codificar cada capa manualmente, pero ahora BNNSGraph toma gráficos completos como objetos, lo que mejora el rendimiento y la eficiencia energética mediante la combinación de transformaciones matemáticas, fusión de capas, elisión de copias y uso compartido de memoria tensorial. BNNSGraph ofrece dos flujos de trabajo principales: usando un paquete CoreML y una compilación Xcode, o definiendo el gráfico directamente en Swift para modelos más pequeños con BNNSGraphBuilder.
- 6:15 - BNNSGraphBuilder
Una nueva API llamada BNNSGraphBuilder permite a los desarrolladores construir gráficos de operaciones directamente en Swift. Esto permite la creación de rutinas de preprocesamiento y posprocesamiento, y pequeños modelos de aprendizaje automático dentro del código Swift. Los beneficios incluyen el uso de un lenguaje familiar, verificación de tipos durante la compilación y la capacidad de compartir valores de tiempo de ejecución entre Swift y el gráfico, lo que genera un mejor rendimiento. La API también proporciona funcionalidades de depuración, como la consulta de tensores intermedios para propiedades como la forma y el tipo de datos, y habilita el autocompletado de Xcode. Un nuevo método de tipo, makeContext, convierte el código Swift en un contexto reutilizable para la ejecución de gráficos. El contexto se crea una vez durante el inicio de la app y luego se puede ejecutar varias veces, beneficiándose de las optimizaciones de gráficos holísticos. La API ofrece una amplia gama de operaciones matemáticas y lógicas, así como soporte para primitivas de redes neuronales comunes, como multiplicación de matrices, convolución y operaciones de reducción. La API BNNSGraphBuilder en Swift aprovecha la tipificación fuerte para detectar errores en el momento de la compilación, lo que garantiza la corrección del tipo de datos para las operaciones tensoriales. Esto se demuestra mediante la conversión automática de tipos de datos, como números enteros, a FP16, lo que evita errores de compilación y mejora la confiabilidad de la app. La API también trata los cortes tensoriales como referencias a datos existentes, optimizando el uso de la memoria. El corte de tensores se realiza mediante subíndices Swift y la nueva estructura SliceRange, lo que lo hace intuitivo para los desarrolladores de Swift. Esto permite operaciones eficientes como el recorte de imágenes, como se muestra en un ejemplo donde se recorta una región cuadrada de una fotografía de una ardilla usando buffers de píxeles vImage y el método withBNNSTensor, que comparte memoria para un mejor rendimiento.
- 16:58 - Uso de BNNSGraphBuilder
BNNSGraphBuilder es una potente API en Swift que te ayuda a construir gráficos de operaciones para el preprocesamiento y posprocesamiento eficiente de datos en apps de aprendizaje automático que usan audio e imágenes. Para el preprocesamiento, usa la API para establecer el umbral de las imágenes y convertir las imágenes de tono continuo en imágenes binarias. También puedes usarlo para tareas de posprocesamiento, como la app de funciones softmax y operaciones topK a los resultados de los modelos de aprendizaje automático. La API demuestra su versatilidad al aplicarse también a efectos de audio. Permite crear efectos de audio en tiempo real como bitcrushing, con importantes mejoras de rendimiento al utilizar una precisión de 16 bits en comparación con 32 bits. BNNSGraphBuilder ofrece una interfaz de programación fácil de usar, lo que lo hace accesible para una amplia gama de apps, desde casos de uso en tiempo real y sensibles a la latencia hasta tareas de aprendizaje automático de propósito general.