View in English

  • Global Nav Open Menu Global Nav Close Menu
  • Apple Developer
Search
Cancel
  • Apple Developer
  • News
  • Discover
  • Design
  • Develop
  • Distribute
  • Support
  • Account
Only search within “”

Quick Links

5 Quick Links

Vidéos

Ouvrir le menu Fermer le menu
  • Collections
  • Sujets
  • Toutes les vidéos
  • À propos

Retour à WWDC25

  • À propos
  • Résumé
  • Transcription
  • Code
  • 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

    • BNNS
    • Supporting real-time ML inference on the CPU
    • vImage.PixelBuffer
      • Vidéo HD
      • Vidéo SD

    Vidéos connexes

    WWDC25

    • Découvrir les frameworks d’apprentissage automatique et d’IA sur les plates-formes Apple

    WWDC24

    • Support real-time ML inference on the CPU
  • Rechercher dans cette vidéo…

    Bonjour, je m’appelle Simon Gladman et je travaille pour le groupe Vector and Numerics d’Apple. Notre groupe offre des bibliothèques pour des calculs performants et économes en énergie sur le CPU. Ces bibliothèques gèrent le traitement de l’image et du signal, l’algèbre linéaire et, ce dont je vais parler aujourd’hui, l’apprentissage automatique. Basic Neural Network Subroutines, ou BNNS, est notre bibliothèque pour l’apprentissage automatique. Elle permet d’ajouter à votre app une inférence basée sur le CPU, idéale pour les cas d’utilisation en temps réel et à faible latence, comme le traitement audio. Vous pouvez l’utiliser pour implémenter des fonctionnalités afin de séparer l’audio pour isoler ou supprimer des voix, segmenter l’audio en zones selon le contenu, ou appliquer un transfert de timbre pour faire sonner un instrument comme un autre.

    BNNS est aussi bien pour traiter d’autres données, comme des images. Si votre app nécessite une inférence performante, BNNS est fait pour vous.

    L’an dernier, nous avons lancé BNNSGraph, une API innovante qui a rendu BNNS plus rapide, plus économe en énergie et plus facile à utiliser. Si vous vous souvenez, nous avons fait la démonstration de BNNSGraph en créant cette Audio Unit Bitcrusher pour vous montrer à quel point il est facile d’ajouter vos propres effets à Logic Pro et Garage Band.

    Cette année, nous améliorons l’API de BNNSGraph pour tirer parti de Swift lors de la création de modèles d’inférence compacts et de graphes d’opérations pour le prétraitement et le post-traitement. Aujourd’hui, je vais parler de notre nouvel ajout à BNNSGraph, BNNSGraphBuilder. Je vais commencer cette séance par un bref récapitulatif de BNNSGraph. Sa définition, son rôle dans l’optimisation de l’AA et des modèles d’IA, et son adoption. Ensuite, je présenterai BNNSGraphBuilder. Je vais résumer le flux de travail nécessaire pour l’implémenter et vous montrer comment écrire un graphe simple. Après avoir présenté BNNSGraphBuilder, je vais effectuer trois démonstrations. Tout d’abord, le prétraitement d’une image avec Graph Builder. Puis, j’utilise la nouvelle API pour post-traiter les données générées par un modèle d’AA. Enfin, je mettrai à jour la démo Bitcrusher de l’an dernier pour créer le graphe des opérations dans Swift. Commençons sans plus attendre. Nous recommandons BNNSGraph pour l’audio et les cas d’utilisation sensibles à la latence, car il permet de contrôler l’allocation de mémoire et le multithreading. Ces deux éléments peuvent modifier le contexte du code noyau et empêcher l’exécution en temps réel des délais. Avant l’an dernier, vous pouviez créer des réseaux basés sur BNNS avec des couches distinctes, comme la convolution, la multiplication matricielle ou l’activation. Pour utiliser BNNS pour un modèle existant, il fallait coder chaque couche comme une primitive BNNS et écrire le code pour tous les tenseurs intermédiaires. BNNSGraph traite un graphe complet constitué de plusieurs couches et de leur flux de données, en le considérant comme un objet unique.

    Vous n’avez donc pas besoin de coder chaque couche individuellement. Vous et vos utilisateurs profiterez de performances accrues et d’une meilleure efficacité énergétique.

    BNNSGraph connaît l’ensemble du modèle. Il peut donc effectuer des optimisations auparavant impossibles. Ces optimisations réduisent le temps d’exécution, l’utilisation de la mémoire et les coûts énergétiques. Mieux encore, elles sont gratuites. Découvrons certaines optimisations à partir de cette section d’un modèle. L’une des optimisations est une transformation mathématique. Par exemple, réorganiser une opération de tranche afin que BNNS n’ait besoin de calculer que le sous-ensemble d’éléments de cette tranche. La fusion des couches est une autre optimisation. Par exemple, fusionner une couche de convolution et une couche d’activation en une seule opération. L’élision de copie évite de copier les données d’une tranche en faisant référence à ce sous-ensemble. BNNSGraph optimise l’utilisation de la mémoire en partageant les tenseurs autant que possible, ce qui élimine les allocations inutiles. Les optimisations de repacking des poids de BNNSGraph améliorent la localité des caches en réorganisant les poids. Pas besoin de coder pour profiter de ces optimisations. Elles se produisent automatiquement. Pour créer un graphe avec l’API basée sur des fichiers introduite l’an dernier, commencez avec un CoreML Package. Xcode compile le package dans un fichier mlmodelc, puis vous écrivez le code pour générer un graphe à partir de ce fichier. La dernière étape consiste à créer un contexte pour envelopper le graphe afin d’effectuer l’inférence. Cette approche reste le meilleur moyen d’intégrer les modèles PyTorch existants dans votre projet. Pour les petits modèles ou graphes d’opérations, un flux de travail plus immédiat est possible. Et si vous pouviez définir les différents éléments de votre graphe dans Swift ? Cette année, nous proposons une nouvelle API, BNNSGraphBuilder, pour effectuer cette opération.

    La nouvelle API BNNSGraphBuilder permet d’écrire des graphes d’opérations avec le langage Swift. Vous pouvez écrire des routines de prétraitement, de post-traitement ou des modèles d’AA dans Swift. Vous créez un contexte avec ce code Swift via un appel de fonction unique, sans étapes intermédiaires. Écrire vos graphes en Swift tout en les intégrant à votre code offre des avantages immédiats. Vous utilisez un langage familier et votre graphe est vérifié lors de la compilation de votre projet Swift. Vous pouvez aussi partager entre Swift et votre graphe des valeurs connues à l’exécution, mais avant la génération du graphe. Si la forme d’un tenseur est connue à l’exécution avant d’initialiser le graphe, intégrez-la directement dans le code pour profiter des performances optimales des tailles statiques par rapport aux tailles flexibles. BNNSGraphBuilder vous permet d’interroger les tenseurs intermédiaires pour des propriétés comme la forme et le type de données afin de faciliter le débogage des graphes. Les tenseurs de type permettent à Xcode d’effectuer une saisie automatique et réduisent le risque d’erreurs d’exécution. Analysons la syntaxe, la vérification de type, les méthodes et opérateurs récents, et l’interrogation des tenseurs intermédiaires, des fonctionnalités auparavant indisponibles. Cette année, nous avons ajouté makeContext, une nouvelle méthode de type, à l’objet BNNSGraph. Elle convertit votre code Swift en un contexte réutilisable que vous utilisez pour exécuter votre graphe. Vous définissez le contexte une seule fois, généralement au démarrage de votre app. Puis, votre app exécute le contexte si nécessaire en profitant des optimisations du traitement holistique du graphe. Faisons un peu de codage pour voir makeContext en action. Cette méthode accepte une fermeture, utilisée pour définir la série d’opérations qui constituent votre graphe.

    Votre graphe accepte généralement un ou plusieurs arguments. Ici, j’utilise le paramètre builder de la fermeture pour spécifier les deux arguments, x et y.

    Les paramètres sont tous deux des vecteurs à huit éléments de valeurs flottantes. Dans cet exemple, le graphe effectue une multiplication élément par élément sur les deux arguments d’entrée et écrit le résultat dans l’argument de sortie nommé product. Mon graphe calcule la valeur moyenne de product et l’écrit dans un deuxième argument de sortie appelé mean, puis le code renvoie ces deux arguments de sortie.

    Pour faciliter le débogage, nous pouvons imprimer les détails des tenseurs intermédiaires et de sortie générés par BNNSGraphBuilder.

    Dans cet exemple, j’ai vérifié que mean a une forme de un, c’est-à-dire qu’il ne contient qu’un seul élément. Après avoir créé le contexte, vous pouvez l’interroger pour dériver les arguments et leurs positions. Ici, le code interroge les noms d’arguments du contexte et crée un array de tenseurs représentant les sorties et les entrées du graphe. BNNSGraph classe les noms d’arguments pour que les sorties précèdent les entrées.

    L’API BNNSGraphBuilder offre des fonctions complètes pour initialiser des arguments à partir de différentes sources de données et pour copier ou référencer des données. Dans cet exemple, le code initialise les entrées des arrays Swift.

    Après allocation des arguments d’entrée et de sortie, l’appel de la fonction execute exécute le graphe et remplit les sorties avec les résultats.

    Et nous pouvons imprimer les résultats en exécutant la fonction.

    Nous ne nous limitons pas à multiplier et à calculer des moyennes. Voyons rapidement BNNSGraphBuilder et découvrons d’autres opérations de la nouvelle API. Voici une sélection des primitives incluses dans BNNSGraphBuilder. Par exemple, multiplication et convolution matricielles, opérations de réduction, collecte et dispersion, ainsi que remplissage, remodelage et transposition. Notre API prend en charge de nombreuses opérations via des opérateurs Swift simples. Nous avons des opérateurs arithmétiques : multiplication, addition, soustraction et division. Nous avons aussi des opérateurs de comparaison par élément, comme l’égalité, l’infériorité et la supériorité. Et pour terminer, nous avons des opérateurs logiques élémentaires. Voilà pour cette brève présentation de l’API. Examinons deux fonctionnalités de l’API GraphBuilder, familières aux développeurs Swift, en commençant par le typage fort. Un typage fort détecte les erreurs à la compilation qui pourraient sinon survenir lors de l’exécution. L’API BNNSGraphBuilder garantit que le type de données des tenseurs est correct pour une opération donnée. Voyons cela en pratique. Dans cet exemple, le graphe affiche les résultats par élément des valeurs flottantes élevées à la puissance des exposants entiers.

    Il est capable d’effectuer ce calcul en convertissant les exposants entiers en FP16. Essayer cette opération sans opération cast empêcherait la compilation de la méthode makeContext. Le graphe masque le résultat en supprimant les éléments dont les valeurs du masque 0 sont inférieures à celles du masque 1. Les éléments tensoriels générés par la comparaison étant booléens, le graphe effectue un autre cast pour multiplier les résultats par la condition. Encore une fois, sans l’opération cast, la compilation de la méthode makeContext ne sera pas exécutée. Il est préférable de détecter ce type d’erreur lors de la compilation, avant que l’app ne soit entre les mains des utilisateurs. Voilà pour le typage fort en pratique. Examinons l’approche de BNNSGraphBuilder pour le slicing, c’est-à-dire la sélection de parties avec un tenseur. Une fonctionnalité que les développeurs Swift connaissent déjà.

    Un slice est en fait une fenêtre sur une partie spécifique d’un tenseur. Par exemple, un slice peut être une seule colonne ou ligne d’une matrice. L’avantage de BNNSGraph est qu’il gère les slices comme des références à des données existantes, sans copier ces données ni allouer plus de mémoire. Le slicing des tenseurs est une opération courante. Notre API GraphBuilder permet de spécifier des slices de tenseur comme des opérations source ou destination. Par exemple, vous pouvez recadrer une image sur une zone spécifique avant de la transmettre à un modèle d’AA. Voyons à quel point il est facile d’utiliser l’API GraphBuilder pour sélectionner une zone carrée au centre de cette photo d’un écureuil savourant son déjeuner. Nous allons définir deux tampons de pixels vImage. Ceux-ci stockent les données des pixels, les dimensions, la profondeur de bits et le nombre de canaux. Le premier tampon, source, contient une photo de l’écureuil. Le deuxième tampon de pixels, Destination, contient le recadrage carré. Un tampon de pixels comprend trois canaux : rouge, vert et bleu, chacun au format 32 bits à virgule flottante. Pour en savoir plus sur les tampons de pixels vImage, consultez la documentation vImage. Les marges horizontales et verticales assurent un recadrage centré de l’image source à 640 x 640. Et voici un graphe qui utilise l’opération de slice pour effectuer le recadrage. D’abord, définissons la source comme argument et spécifions la hauteur et la largeur comme tailles flexibles. Le passage d’une valeur négative, ici moins 1, indique à BNNSGraph que ces dimensions peuvent avoir une quelconque valeur. La valeur finale de la forme 3 spécifie que l’image contient trois canaux : rouge, vert et bleu. L’API BNNSGraphBuilder utilise des indices Swift pour effectuer une opération de slice. Ici, le code utilise la nouvelle structure SliceRange. Les indices de départ pour les dimensions verticales et horizontales sont les valeurs de marge correspondantes. Définir les indices de fin comme une marge négative signifie que l’index final est la dernière valeur de cette dimension moins la marge.

    Ici, nous ne voulons pas rogner le long de la dimension de canal, et le code utilise fillAll pour inclure les trois canaux.

    Cette année, nous introduisons une nouvelle méthode pour les tampons de pixels vImage. La méthode withBNNSTensor crée un BNNSTensor temporaire qui partage la mémoire, la taille et le nombre de canaux avec le tampon de pixels. La nouvelle méthode simplifie le transfert d’images vers et depuis votre graphe, comme le montre le code. La mémoire étant partagée, il n’y a ni copie ni allocation, ce qui offre d’excellentes performances pour le traitement d’images. Une fois la méthode de fonction d’exécution retournée, le tampon de pixels de destination contient une image coupée. Nous pouvons vérifier que notre écureuil est bien recadré en créant une image Core Graphics du résultat. En plus de la nouvelle structure de plage de slices GraphBuilder, l’API de slicing des tenseurs prend en charge tous les types de plages Swift. Peu importe vos besoins en slicing, nous avons la solution qui vous convient. Et voilà un aperçu du slicing. Nous avons examiné BNNSGraphBuilder et certaines de ses fonctionnalités. Voyons maintenant quelques cas d’utilisation. BNNSGraphBuilder est une API idéale pour créer des graphes d’opérations pour le prétraitement et le post-traitement des données échangées avec les modèles d’AA. Appliquer un seuil à une image est une technique de prétraitement qui convertit une image à tons continus en une image binaire avec seulement des pixels noirs ou blancs. Voyons comment l’implémenter avec GraphBuilder.

    Avant de commencer à écrire le graphe, nous allons définir des tampons de pixels vImage. Le premier stocke l’image source, et le second est la destination qui reçoit l’image traitée. Ces deux tampons de pixels sont au format à virgule flottante 16 bits à canal unique. La première étape du graphe consiste à définir la source, qui représente l’image d’entrée. Le code utilise des valeurs négatives pour la taille de l’image pour indiquer que le graphe accepte toute taille d’image, tout en spécifiant le type de données FP16 pour les tampons de pixels. Le calcul de la valeur moyenne des pixels continus en niveaux de gris consiste simplement à appeler la méthode mean. L’opérateur remplit le tenseur à seuil avec des uns pour les pixels dont la valeur est supérieure à la moyenne des pixels et avec des zéros pour les autres pixels. Enfin, le graphe applique les uns et les zéros booléens à la profondeur de bits du tampon de pixels de destination. Nous utilisons à nouveau la méthode withBNNSTensor pour passer les deux tampons d’image au contexte et exécuter la fonction pour générer l’image traitée.

    Après avoir converti cette image en niveaux de gris continus d’un couple de pélicans en vol sur le graphe, nous obtenons cette image traitée avec des pixels uniquement noirs ou blancs. Le post-traitement des résultats d’un modèle d’AA est un autre cas d’utilisation de l’API BNNSGraphBuilder.

    Supposons que nous appliquions une fonction softmax suivie d’une opération topK aux résultats d’un modèle d’AA. La fonction de post-traitement génère un petit graphe instantanément. Le graphe déclare un argument d’entrée basé sur le tenseur d’entrée de la fonction. Il applique ensuite une opération softmax à l’argument d’entrée, puis calcule ses valeurs et indices topK. Notez que le paramètre k que le code transmet à la fonction topK est en fait défini en dehors de la fermeture de makeContext. Enfin, le graphe renvoie les résultats topK. Après avoir défini le graphe, la fonction déclare les tenseurs qui stockent les résultats et transmet les tenseurs de sortie et d’entrée au contexte. Enfin, la méthode makeArray convertit les valeurs topK et les index en arrays Swift et les renvoie. Nous avons là quelques exemples de données de prétraitement et de post-traitement avec l’API GraphBuilder. Regardons la démo Bitcrusher de l’an dernier et mettons à jour la section Swift pour utiliser BNNSGraphBuilder. Ce code montre comment BNNSGraph facilite l’ajout d’effets audio basés sur l’AA.

    L’année dernière, nous avons fait la démonstration de BNNSGraph en l’intégrant dans une app d’extension Audio Unit. Cette app a intégré un effet Bitcrusher en temps réel à un signal audio, et la démonstration a utilisé Swift pour appliquer cet effet à une onde sinusoïdale afin d’afficher une représentation visuelle dans l’interface utilisateur. Avec cette onde sinusoïdale, le graphe peut réduire la résolution pour ajouter de la distorsion, et la représentation visuelle montre l’onde comme une série d’étapes au lieu d’une onde continue. Nous avons aussi ajouté un gain de saturation pour enrichir le son. L’an dernier, la démonstration utilisait un package CoreML basé sur PyTorch. Comparons le code de l’année dernière aux mêmes fonctionnalités dans Swift avec la nouvelle API GraphBuilder. Voici le PyTorch à gauche et le nouveau code Swift à droite. Une différence immédiate est que Swift utilise let et var pour définir les tenseurs intermédiaires. Vous pouvez donc choisir leur immuabilité ou mutabilité. Aucune importation supplémentaire n’est nécessaire pour accéder à toutes les opérations disponibles. De plus, les opérations comme tanh sont des méthodes sur tenseurs, non des fonctions libres.

    Les opérateurs arithmétiques par élément sont les mêmes avec les deux approches différentes.

    Une différence supplémentaire est que la fermeture makeContext peut renvoyer plusieurs tenseurs de sortie, donc vous retournez toujours un array. Swift GraphBuilder vous permet de définir des arguments d’entrée à l’intérieur de la fermeture makeContext. Si vous avez un modèle PyTorch, nous recommandons d’utiliser ce code et l’API basée sur des fichiers. Pour les nouveaux projets, notre API GraphBuilder permet d’écrire des graphes en Swift avec une structure similaire aux opérations écrites avec PyTorch, comme le montre cette comparaison. Nous pouvons aussi démontrer la prise en charge 16 bits de BNNSGraph et comment, dans cet exemple, elle est plus rapide que les opérations FP32. Voici à nouveau notre code Swift, mais cette fois en utilisant un alias de type pour spécifier la précision.

    La modification de l’alias de type modifie le graphe pour utiliser FP16 plutôt que FP32. Dans cet exemple, FP16 est beaucoup plus rapide que l’équivalent du FP32. Pour conclure, BNNSGraphBuilder offre une API Swift intuitive pour créer des modèles et des graphes d’opérations efficaces et économes en énergie. Elle est idéale pour les cas d’utilisation en temps réel et sensibles à la latence, mais son interface de programmation conviviale permet un large éventail d’applications. Avant de terminer, je vous recommande de consulter notre documentation, où vous trouverez des documents de référence sur BNNSGraph et BNNSGraphBuilder. Regardez aussi notre vidéo de l’an dernier, où je discute de l’API basée sur des fichiers et de l’utilisation de BNNSGraph dans C.

    Merci pour votre attention aujourd’hui.

    • 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.

Developer Footer

  • Vidéos
  • WWDC25
  • Quoi de neuf dans BNNS Graph
  • Open Menu Close Menu
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • Icon Composer
    • SF Symbols
    Open Menu Close Menu
    • Accessibility
    • Accessories
    • App Store
    • Audio & Video
    • Augmented Reality
    • Business
    • Design
    • Distribution
    • Education
    • Fonts
    • Games
    • Health & Fitness
    • In-App Purchase
    • Localization
    • Maps & Location
    • Machine Learning & AI
    • Open Source
    • Security
    • Safari & Web
    Open Menu Close Menu
    • Documentation
    • Sample Code
    • Tutorials
    • Downloads
    • Forums
    • Videos
    Open Menu Close Menu
    • Support Articles
    • Contact Us
    • Bug Reporting
    • System Status
    Open Menu Close Menu
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles
    • Feedback Assistant
    Open Menu Close Menu
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program
    • News Partner Program
    • Video Partner Program
    • Security Bounty Program
    • Security Research Device Program
    Open Menu Close Menu
    • Meet with Apple
    • Apple Developer Centers
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Academies
    • WWDC
    Get the Apple Developer app.
    Copyright © 2025 Apple Inc. All rights reserved.
    Terms of Use Privacy Policy Agreements and Guidelines