-
Quoi de neuf dans BNNS Graph
L'API BNNS Graph Builder permet désormais aux développeurs d'écrire des graphiques d'opérations en utilisant le langage Swift familier pour générer des routines de pré et post-traitement et de petits modèles d'apprentissage automatique. BNNS compile des graphiques avant l'exécution et prend en charge les cas d'utilisation en temps réel et sensibles à la latence, tels que le traitement audio. Dans cette séance, nous reprenons l'exemple du bit-crusher de l'année dernière et simplifions le composant Swift en supprimant la dépendance à un fichier Python séparé et en implémentant l'effet audio entièrement dans Swift. L'API BNNS Graph Builder est également adaptée au prétraitement des données d'image avant de les transmettre à un modèle d'apprentissage automatique. La séance comprend également une démonstration du découpage des pixels transparents d'une image avec un canal alpha.
Chapitres
- 0:00 - Introduction
- 3:12 - Récapitulatif BNNSGraph
- 6:15 - BNNSGraphBuilder
- 16:58 - Utilisation de BNNSGraphBuilder
Ressources
Vidéos connexes
WWDC25
WWDC24
-
Rechercher dans cette vidéo…
-
-
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 - Introduction
Le groupe Vector and Numerics d’Apple a développé BNNS, une bibliothèque d’apprentissage automatique pour l’inférence basée sur le CPU dans les apps, particulièrement utile pour le traitement de l’audio et de l’image en temps réel. BNNSGraph, introduit l’année dernière, a amélioré la vitesse, l’efficacité et la facilité d’utilisation. BNNSGraphBuilder, qui permet de créer en Swift de petits modèles et graphiques pour le pré-traitement et le post-traitement, est maintenant ajouté. Ceci est démontré par le prétraitement d’image, le post-traitement des données et la mise à jour de l’échantillon d’unité audio Bitcrusher.
- 3:12 - Récapitulatif BNNSGraph
BNNSGraph est recommandé pour les tâches audio et à faible latence avec des délais en temps réel, car il permet le contrôle de la mémoire et du multithreading, améliorant ainsi les performances en temps réel. Auparavant, BNNS nécessitait le codage manuel de chaque couche, mais BNNSGraph utilise maintenant des graphes entiers en tant qu’objets. Il optimise ainsi les performances et l’efficacité énergétique en combinant des transformations mathématiques, la fusion de couches, l’élision de copie et le partage de mémoire tensorielle. BNNSGraph propose deux flux de travail principaux : l’utilisation d’un package CoreML et d’une compilation Xcode ou la définition du graphe directement en Swift pour les petits modèles avec BNNSGraphBuilder.
- 6:15 - BNNSGraphBuilder
Une nouvelle API appelée BNNSGraphBuilder permet aux développeurs de créer des graphes d’opérations directement en Swift. Cela permet de créer des routines de prétraitement et de post-traitement ainsi que de petits modèles d’apprentissage automatique dans le code Swift. Les avantages incluent l’utilisation d’un langage familier, la vérification de type pendant la compilation et la possibilité de partager les valeurs d’exécution entre Swift et le graphe, ce qui améliore les performances. L’API propose également des fonctionnalités de débogage, telles que l’interrogation de tenseurs intermédiaires pour des propriétés telles que la forme et le type de données, et active la saisie automatique Xcode. Une nouvelle méthode de type, 'makeContext', convertit le code Swift en contexte réutilisable pour l’exécution de graphes. Le contexte est créé une fois au démarrage de l’app et peut ensuite être exécuté plusieurs fois, en bénéficiant d’optimisations de graphes holistiques. L’API offre un large éventail d’opérations mathématiques et logiques, ainsi que la prise en charge des primitives courantes des réseaux neuronaux telles que les opérations de multiplication matricielle, de convolution et de réduction. L’API BNNSGraphBuilder de Swift exploite un typage fort pour détecter les erreurs au moment de la compilation, garantissant ainsi l’exactitude du type de données pour les opérations de tenseurs. Ceci est démontré par la conversion automatique des types de données, tels que les entiers en FP16, ce qui évite les erreurs de compilation et améliore la fiabilité de l’app. L’API traite également les tranches de tenseur comme des références à des données existantes, optimisant ainsi l’utilisation de la mémoire. Le slicing des tenseurs s’effectue à l’aide d’indices Swift et de la nouvelle structure SliceRange, ce qui le rend plus intuitif pour les développeurs Swift. Cela permet des opérations efficaces telles que le recadrage d’image, comme le montre un exemple où une région carrée est recadrée à partir d’une photographie d’un écureuil à l’aide de tampons de pixels vImage et de la méthode 'withBNNSTensor', qui partage la mémoire pour des performances améliorées.
- 16:58 - Utilisation de BNNSGraphBuilder
BNNSGraphBuilder est une puissante API Swift qui vous aide à créer des graphes d’opérations pour un prétraitement et un post-traitement efficaces des données dans les applications d’apprentissage automatique utilisant de l’audio et des images. Pour le prétraitement, utilisez l’API pour chiffrer les images, en convertissant les images à tons continus en images binaires. Vous pouvez également l’utiliser pour des tâches de post-traitement telles que l’application de fonctions softmax et d’opérations topK aux résultats des modèles ML. L’API démontre sa polyvalence en s’appliquant également aux effets audio. Elle vous permet de créer des effets audio en temps réel, comme le bitcrushing, avec des améliorations significatives des performances en utilisant une précision de 16 bits par rapport à 32 bits. BNNSGraphBuilder offre une interface de programmation conviviale, ce qui la rend accessible à un large éventail d’applications, allant des cas d’utilisation en temps réel et sensibles à la latence aux tâches ML à usage général.