-
Novidades do BNNSGraph
A API BNNSGraphBuilder agora permite que os desenvolvedores criem gráficos de operações usando a linguagem que já conhecem do Swift para gerar rotinas de pré e pós-processamento e pequenos modelos de aprendizado de máquina. O BNNS compila os gráficos antes da execução e oferece suporte em casos de uso em tempo real e sensíveis a à latência, como o processamento de áudio. Nesta sessão, revisitamos o exemplo do bit-crusher apresentado no ano passado e simplificamos o componente do Swift, eliminando a dependência de um arquivo Python separado. Em vez disso, implementamos o efeito de áudio inteiramente no Swift. A API BNNSGraphBuilder também pode ser usada no pré-processamento de dados de imagem antes de mandá-los para um modelo de aprendizado de máquina. A sessão também inclui uma demonstração de recorte dos pixels transparentes de uma imagem com um canal alfa.
Capítulos
- 0:00 - Introdução
- 3:12 - Revisão do BNNSGraph
- 6:15 - BNNSGraphBuilder
- 16:58 - Usar o BNNSGraphBuilder
Recursos
Vídeos relacionados
WWDC25
WWDC24
-
Buscar neste vídeo...
-
-
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 - Introdução
O grupo de vetores e valores numéricos da Apple desenvolveu o BNNS, uma biblioteca de aprendizado de máquina para inferência baseada em CPU em apps, particularmente útil para processamento de áudio e imagens em tempo real. O BNNSGraph, introduzido no ano passado, melhorou a velocidade, eficiência e facilidade de uso. Agora, o BNNSGraphBuilder é adicionado, permitindo a criação baseada em Swift de pequenos modelos e gráficos para pré e pós-processamento. Isso é demonstrado com pré-processamento de imagem, pós-processamento de dados e atualização da amostra de unidade de áudio Bitcrusher.
- 3:12 - Revisão do BNNSGraph
O BNNSGraph é recomendado para tarefas de áudio e baixa latência com prazos em tempo real, pois permite o controle de memória e multithreading, melhorando o desempenho em tempo real. Antes, o BNNS exigia codificar cada camada manualmente, mas agora o BNNSGraph usa gráficos inteiros como objetos, otimizando o desempenho e a eficiência energética por meio da combinação de transformações matemáticas, fusão de camadas, elisão de cópias e compartilhamento de memória tensorial. O BNNSGraph oferece dois fluxos de trabalho principais: usar um pacote CoreML e compilação Xcode, ou definir o gráfico diretamente no Swift para modelos menores com BNNSGraphBuilder.
- 6:15 - BNNSGraphBuilder
Com uma nova API chamada BNNSGraphBuilder, desenvolvedores podem criar gráficos de operações diretamente em Swift. Isso permite a criação de rotinas de pré e pós-processamento e pequenos modelos de aprendizado de máquina dentro do código Swift. Os benefícios incluem o uso de uma linguagem familiar, a verificação de tipos durante a compilação e a capacidade de compartilhar valores de tempo de execução entre Swift e o gráfico, levando a um melhor desempenho. A API também fornece recursos de depuração, como a consulta de tensores intermediários para propriedades como forma e tipo de dados, e habilita o preenchimento automático do Xcode. Um novo método de tipo, "makeContext", converte código Swift em um contexto reutilizável para execução de gráfico. O contexto é criado uma vez durante a inicialização do app e pode ser executado várias vezes, beneficiando-se de otimizações de gráficos holísticos. A API oferece uma ampla gama de operações matemáticas e lógicas, bem como suporte para primitivas de redes neurais comuns, como operações de multiplicação, convolução e redução de matrizes. A API BNNSGraphBuilder no Swift utiliza a sólida digitação para detectar erros em tempo de compilação, garantindo a correção do tipo de dados para operações tensoriais. Isso é demonstrado por meio da conversão automática de tipos de dados, como inteiros para FP16, o que evita erros de compilação e aumenta a confiabilidade do app. A API também trata partes de tensor como referências a dados existentes, otimizando o uso de memória. A divisão de tensores é realizada usando subscritos Swift e a nova estrutura SliceRange, tornando-o intuitivo desenvolvedores do Swift. Isso permite operações eficientes como corte de imagem, como mostrado em um exemplo em que uma região quadrada é cortada de uma fotografia de um esquilo usando buffers de pixel vImage e o método "withBNNSTensor", que compartilha memória para melhorar o desempenho.
- 16:58 - Usar o BNNSGraphBuilder
O BNNSGraphBuilder é uma poderosa API no Swift que ajuda a criar gráficos de operações para pré e pós-processamento eficiente de dados em apps de aprendizado de máquina utilizando áudio e imagens. Para pré-processamento, use a API para limitar imagens, convertendo imagens de tom contínuo em imagens binárias. Você também pode usá-la para tarefas de pós-processamento, como aplicar funções softmax e operações topK aos resultados de modelos de ML. A API demonstra sua versatilidade sendo aplicada também a efeitos de áudio. Ela permite que criar efeitos de áudio em tempo real, como bitcrushing, com melhorias significativas de desempenho ao usar a precisão de 16 bits em comparação com a de 32 bits. O BNNSGraphBuilder oferece uma interface de programação fácil de usar, o que a torna acessível para uma ampla gama de apps, desde casos de uso sensíveis à latência e em tempo real até tarefas de ML de uso geral.