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

Vídeos

Abrir menu Fechar menu
  • Coleções
  • Tópicos
  • Todos os vídeos
  • Sobre

Voltar para WWDC25

  • Sobre
  • Resumo
  • Transcrição
  • Código
  • 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

    • BNNS
    • Supporting real-time ML inference on the CPU
    • vImage.PixelBuffer
      • Vídeo HD
      • Vídeo SD

    Vídeos relacionados

    WWDC25

    • Conheça os frameworks de aprendizado de máquina e IA nas plataformas Apple

    WWDC24

    • Support real-time ML inference on the CPU
  • Buscar neste vídeo...

    Oi, meu nome é Simon Gladman, e trabalho na equipe Vector and Numerics Group na Apple. Nosso grupo fornece um pacote de bibliotecas para computação de alto desempenho e eficiência energética na CPU. Essas bibliotecas possibilitam, por exemplo, o processamento de imagens e sinais, álgebra linear e o assunto de hoje: aprendizado de máquina (ML).

    Basic Neural Network Subroutines (BNNS) é nossa biblioteca de ML. Ela permite adicionar inferência baseada em CPU a apps e é ideal para casos de uso em tempo real e de baixa latência, como processamento de áudio. Você pode usá-la para implementar funcionalidades como: Separar o áudio para isolar ou remover vocais, segmentar o áudio em regiões com base no conteúdo ou transferir timbre para fazer um instrumento soar como outro.

    Mas a BNNS também é ideal para trabalhar com outros dados, como imagens. Se o seu app exigir inferência de alto desempenho, a BNNS é ideal para você.

    No ano passado, lançamos a BNNSGraph, uma API que aumentou a rapidez, a eficiência energética e a facilidade de trabalhar com a BNNS. E demonstramos a BNNSGraph criando esta unidade de áudio do Bitcrusher,

    que mostra como é fácil adicionar seus efeitos ao Logic Pro e ao GarageBand.

    Neste ano, a BNNSGraph ganha uma interface de programação ainda melhor que permite aproveitar o poder do Swift para criar pequenos modelos para inferência e gráficos de operações para pré e pós-processamento. Hoje, falarei sobre nossa nova adição à BNNSGraph: a BNNSGraphBuilder. Vou iniciar a sessão com um resumo da BNNSGraph. O que é, como ela pode otimizar o ML e os modelos de IA e como você pode adotá-la. Depois, vou apresentar a BNNSGraphBuilder. Resumirei o fluxo de trabalho superfácil necessário para implementá-la e, como parte desta introdução, demonstrarei como escrever um gráfico simples. Depois de apresentar a BNNSGraphBuilder, farei três demonstrações. Primeiro, como pré-processar uma imagem com o Graph Builder. Como usar a nova API para pós-processar dados gerados por um modelo de ML. Por fim, vou atualizar a demonstração do Bitcrusher do ano passado para criar o gráfico de operações em Swift. E agora, sem mais delongas, vamos começar. Recomendamos a BNNSGraph para áudio e outros casos de uso relacionados à latência, pois oferece controle sobre alocação de memória e multithreading. Ambos podem mudar o contexto no código do kernel e levar à derrota de prazos em tempo real. Antes do ano passado, você criava redes baseadas em BNNS com camadas discretas, como convolução, multiplicação de matrizes ou ativação. Se quisesse usar a BNNS para implantar um modelo, teria que codificar cada camada como primitiva da BNNS e escrever o código para os tensores intermediários. A BNNSGraph pega um gráfico inteiro com várias camadas e o fluxo de dados entre elas e o consome como um único objeto gráfico.

    Isso significa que você não precisa escrever o código para cada camada. Ademais, você e seus usuários se beneficiarão de desempenho mais rápido e melhor eficiência energética.

    Como a BNNSGraph conhece todo o modelo, ela é capaz de realizar uma série de otimizações que não eram possíveis antes. Essas otimizações reduzem tempo de execução, uso de memória e custo de energia. E melhor ainda, elas são gratuitas. Nesta pequena seção de um modelo, vamos explorar algumas dessas otimizações. Uma das otimizações é uma transformação matemática. Por exemplo, reordenar uma operação de segmentação para que a BNNS só precise calcular o subconjunto de elementos nesse segmento. Outra otimização é a fusão de camadas. Por exemplo, fundir uma camada de convolução e outra de ativação em uma única operação. E a elisão de cópias evita a cópia dos dados em um segmento referenciando esse subconjunto de dados. Ao garantir que os tensores compartilhem memória quando possível, a BNNSGraph otimiza o uso de memória e elimina alocações desnecessárias. Por fim, as otimizações de reacondicionamento podem reacondicionar pesos para melhorar a localização de cache. Você não precisa escrever códigos para aproveitar essas otimizações. Elas simplesmente acontecem. Para criar um gráfico usando a API baseada em arquivo lançada no ano passado, comece com um pacote CoreML. O Xcode compila automaticamente o pacote em um arquivo mlmodelc, e você escreve o código que cria um gráfico a partir desse arquivo. A última etapa é criar um contexto para encapsular o gráfico, e é esse contexto que realiza a inferência. Essa abordagem ainda é a melhor forma de integrar modelos PyTorch existentes ao seu projeto. Para modelos pequenos ou gráficos de operações, você pode se beneficiar de um fluxo de trabalho mais imediato. E se você pudesse definir os elementos individuais do seu gráfico em Swift? Neste ano, lançaremos uma API que faz exatamente isso: a BNNSGraphBuilder.

    A API BNNSGraphBuilder permite que você escreva gráficos de operações usando a linguagem conhecida do Swift. Você pode escrever rotinas de pré e pós-processamento ou pequenos modelos de ML em Swift. Você cria um contexto a partir do código Swift com uma chamada de função e sem etapas intermediárias. Escrever gráficos em Swift e alinhados com seus outros códigos traz benefícios. Você usa uma linguagem conhecida, e o gráfico passa pela verificação de tipos quando o projeto do Swift é compilado. Você pode compartilhar valores entre o Swift e seu gráfico que são conhecidos no tempo de execução, mas antes de gerar o gráfico. Se você souber a forma de um tensor no tempo de execução antes de inicializar o gráfico, poderá passá-la para o código do gráfico e aproveitar o melhor desempenho dos tamanhos estáticos em relação aos flexíveis. A BNNSGraphBuilder também permite consultar tensores intermediários para propriedades como forma e tipo de dados, o que ajuda a depurar gráficos. Tensores de tipo permitem o preenchimento automático do Xcode e reduzem a chance de erros no tempo de execução. Vejamos a nova sintaxe, a verificação de tipos, os novos métodos e operadores e como consultar tensores intermediários, recursos que não estavam disponíveis.

    Neste ano, adicionamos um método de tipo ao objeto BNNSGraph, chamado makeContext. É esse novo método que converte o código do Swift em um contexto reutilizável usado para executar seu gráfico. Você cria o contexto uma vez, normalmente durante a inicialização do app. Seu app executa o contexto quando necessário e se beneficia das otimizações decorrentes do tratamento holístico do gráfico. Com programação ao vivo, vamos ver makeContext em ação. Esse método aceita um fechamento, e é esse fechamento que você usa para definir a série de operações que constituem seu gráfico.

    Seu gráfico normalmente aceita um ou mais argumentos. Usarei o parâmetro builder do fechamento para especificar os dois artgumentos, x e y.

    Os parâmetros são oito vetores de elementos de valores flutuantes. Aqui, o gráfico multiplica elemento por elemento nos dois argumentos de entrada

    e grava o resultado no argumento de saída, que chamei de product. Meu gráfico também calcula o valor médio do produto e grava o valor em um segundo argumento de saída, que chamei de mean. Por fim, o código retorna esses dois argumentos de saída.

    Para depuração, podemos imprimir detalhes dos tensores intermediários e de saída que a BNNSGraphBuilder gera.

    Neste exemplo, imprimi a forma da média para garantir que tenha a forma de um, ou seja, contenha apenas um elemento. Depois que o contexto é criado, você pode consultá-lo para gerar os argumentos e suas posições. Aqui, o código consulta os nomes de argumento do contexto e cria uma matriz de tensores que representam as saídas e entradas do gráfico. A BNNSGraph ordena os nomes de argumento, exibindo as saídas primeiro e as entradas depois.

    A API BNNSGraphBuilder fornece um conjunto robusto de funções para inicializar argumentos de diferentes fontes de dados e copiar ou referenciar dados. Neste exemplo, o código inicializa as entradas de matrizes Swift.

    Com os argumentos de entrada e saída alocados, chamar execute function executa o gráfico e preenche as duas saídas com os resultados.

    E podemos imprimir os resultados executando a função.

    Mas não estamos limitados apenas a multiplicar e calcular médias. Vamos fazer um rápido tour pela BNNSGraphBuilder e explorar algumas das outras operações que a nova API fornece. Veja uma pequena seleção das primitivas incluídas na BNNSGraphBuilder. Multiplicação e convolução de matrizes, operações de redução, operações de coleta e dispersão e operações como preenchimento, remodelação e transposição. A API permite realizar muitas operações como operadores simples do Swift. Assim, temos operadores aritméticos, multiplicação, adição, subtração e divisão. Além de operadores de comparação no nível do elemento, incluindo igualdade, menor que e maior que. E para completar o conjunto, operadores lógicos no nível do elemento. Essa foi uma introdução rápida à API em si. Vamos nos aprofundar em dois recursos da API do GraphBuilder conhecidos pelos desenvolvedores em Swift, começando pela tipagem forte. Ela ajuda a detectar erros no tempo de compilação que, caso contrário, podem acontecer no tempo de execução. A API BNNSGraphBuilder garante que o tipo de dados para tensores esteja correto para uma dada operação. Vejamos isso em ação. O gráfico retorna os resultados no nível do elemento de valores base de ponto flutuante elevados à potência de expoentes inteiros. Ele é capaz de realizar esse cálculo convertendo os expoentes inteiros para FP16.

    Tentar executar isso sem a conversão impediria que o método make context fosse compilado. O gráfico também mascara o resultado zerando elementos em que os valores em mask0 são menores que os valores correspondentes em mask1.

    Como os elementos de tensores que a comparação gera são booleanos, o gráfico realiza outra conversão para multiplicar os resultados pela condição. Mais uma vez, sem a operação de conversão, o método make context não seria compilado. É sempre melhor detectar esse tipo de erro no tempo de compilação, antes que seu app esteja nas mãos dos usuários. Essa é a tipagem forte em ação. Vejamos a abordagem da BNNSGraphBuilder à segmentação, ou seja, a seleção de partes com o tensor. Isso também será familiar para desenvolvedores Swift.

    Um segmento é uma janela para uma parte específica de um tensor. Um segmento pode ser uma única coluna ou linha de uma matriz. O bom da BNNSGraph é que ela trata segmentos como referências a dados existentes, sem a sobrecarga de copiar esses dados ou alocar mais memória. Segmentar tensores é uma operação comum. A nova API do GraphBuilder permite especificar segmentos de um tensor como as operações de origem ou destino.

    Talvez você queira cortar uma imagem para uma região de interesse antes de passá-la para um modelo de ML. Vejamos como é fácil usar a API GraphBuilder para selecionar uma região quadrada no centro desta fotografia de um esquilo desfrutando de seu almoço. Definiremos dois buffers de pixels vImage. Eles armazenam em imagens dados de pixel, dimensões, profundidade de bits e número de canais. O primeiro buffer, Source, contém uma fotografia do esquilo. O segundo buffer de pixels, Destination, conterá o corte quadrado. Cada buffer de pixel tem três canais, vermelho, verde e azul, e cada canal está no formato de ponto flutuante de 32 bits. Saiba mais sobre buffers de pixels vImage na documentação do vImage. As margens horizontal e vertical garantem que o corte de 640 x 640 esteja centralizado na imagem de origem. Este é um gráfico que usa a operação de segmentação para executar o corte. Definiremos a origem como um argumento e especificaremos a altura e a largura como tamanhos flexíveis.

    Enviar um valor negativo, -1 neste caso, diz à BNNSGraph que essas dimensões podem ser de qualquer valor. O valor final na forma 3 especifica que a imagem contém três canais: vermelho, verde e azul. A API BNNSGraphBuilder usa subscritos Swift para executar uma operação de segmentação. Neste exemplo, o código usa a nova estrutura SliceRange. Os índices iniciais para as dimensões vertical e horizontal são os valores de margem correspondentes. Definir os índices finais como o valor de margem negativo indica que o índice final é o valor final dessa dimensão menos a margem.

    Neste exemplo, não queremos cortar ao longo da dimensão do canal, e o código especifica fillAll para garantir a inclusão de todos os três canais.

    Neste ano, estamos lançando um método para buffers de pixels vImage. O método withBNNSTensor permite criar um BNNSTensor temporário que compartilha memória e propriedades como tamanho e contagem de canais com o buffer de pixels. Como este código demonstra, o novo método torna a passagem de imagens de e para o seu gráfico superfácil. E, como a memória é compartilhada, não há cópia ou alocação, o que significa um ótimo desempenho ao trabalhar com imagens. Após o retorno do método execute function, o buffer de pixels de destino contém uma imagem cortada. Podemos verificar se o esquilo foi cortado com segurança criando uma imagem do Core Graphics do resultado. Além da estrutura de intervalos de segmentos do GraphBuilder, a API de segmentação de tensores aceita todos os tipos de intervalo do Swift. Atendemos a todas as suas necessidades de segmentação. Essa foi uma visão geral da segmentação. Agora que analisamos a BNNSGraphBuilder e alguns de seus recursos, vamos mergulhar nos casos de uso. A BNNSGraphBuilder é uma ótima API para construir gráficos de operações para dados de pré e pós-processamento enviados ou recebidos de modelos de ML.

    Uma técnica de pré-processamento é limitar uma imagem. Converter uma imagem de tom contínuo em uma uma imagem binária apenas com pixels pretos ou brancos. Vamos ver como podemos implementar isso usando o GraphBuilder.

    Antes de escrever o gráfico, definiremos novamente alguns buffers de pixels vImage. O primeiro armazena a imagem de origem e o segundo é o destino que recebe a imagem limitada. Ambos os buffers estão no formato de ponto flutuante de 16 bits de canal único. O primeiro passo no gráfico é definir a origem, que representa a imagem de entrada. O código passa valores negativos para o tamanho da imagem para especificar que o gráfico dá suporte a qualquer tamanho, mas especifica o tipo de dados FP16 que corresponde aos buffers de pixels. Calcular o valor médio dos pixels contínuos em escala de cinza é uma questão de chamar o método mean. O operador no nível do elemento maior que preenche o tensor limitado com uns para pixels correspondentes do valor maior que o valor médio de pixels, e zeros caso contrário. Por fim, o gráfico converte os uns e zeros booleanos para a profundidade de bits do buffer de pixels de destino. Usaremos o método withBNNSTensor novamente para passar os dois buffers de imagens para o contexto e executar a função para gerar a imagem limitada.

    Após passar esta imagem em escala de cinza contínua de um par de pelicanos em voo para o gráfico, obtemos esta imagem limitada onde todos os pixels são pretos ou brancos. Outro caso de uso para a API BNNSGraphBuilder é o pós-processamento dos resultados de um modelo de ML.

    Digamos que queiramos aplicar uma função softmax seguida por uma operação topK aos resultados de um modelo de ML. A função postProcess cria um pequeno gráfico em tempo real. O gráfico declara um argumento de entrada baseado no tensor de entrada da função. Depois, aplica uma operação softmax ao argumento de entrada e calcula seus valores e índices topK. Observe que o parâmetro k que o código passa para a função topK é definido fora do fechamento make context.

    Por fim, o gráfico retorna os resultados topK. Depois de definir o gráfico, a função declara os tensores que armazenam os resultados e passa os tensores de saída e entrada para o contexto.

    Por fim, o método makeArray converte os valores e índices topK em matrizes do Swift e as retorna.

    Esse foram exemplos de pré e pós-processamento de dados com a API do GraphBuilder. Agora, vamos dar uma olhada na demonstração do Bitcrusher do ano passado e atualizar a parte Swift para usar a BNNSGraphBuilder. Este código mostra como a BNNSGraph facilita muito a adição de efeitos de áudio baseados em ML. No ano passado, demonstramos a BNNSGraph incorporando-a a um app de extensão da Audio Unit. Ele adicionou um efeito Bitcrusher em tempo real a um sinal de áudio, e a demonstração usou o Swift para aplicar o mesmo efeito a uma onda senoidal para exibir uma representação visual do efeito na interface. Considerando esta onda senoidal, o gráfico é capaz de reduzir a resolução para adicionar distorção ao som, e a representação visual mostra a onda senoidal como uma série discreta de etapas em vez de uma onda contínua. Também incluímos ganho de saturação para adicionar riqueza ao som. A demonstração do ano passado usou um pacote CoreML baseado em código PyTorch. Vamos pegar o código do ano passado e compará-lo com a mesma funcionalidade escrita em Swift usando a nova API do GraphBuilder. Aqui está o PyTorch à esquerda e o novo código Swift à direita. Uma diferença imediata é que o Swift usa let e var para definir tensores intermediários, então você pode decidir se eles são imutáveis ou mutáveis. Você não precisa de importações extras para acessar toda a gama de operações disponíveis. Além disso, operações como, neste caso, tanh, são métodos em tensores em vez de funções livres.

    Os operadores aritméticos no nível do elemento são os mesmos com as duas abordagens.

    Outra diferença, porém, é que o fechamento make context pode retornar mais de um tensor de saída, então você sempre retorna uma matriz. O GraphBuilder do Swift permite definir argumentos de entrada dentro do fechamento make context. Se já tiver um modelo PyTorch, ainda é recomendável usar esse código e a API baseada em arquivo existente. Para novos projetos, a nova API do GraphBuilder permite escrever gráficos usando o Swift, e como esta comparação demonstra, com uma estrutura semelhante ao mesmo conjunto de operações escritas usando o PyTorch. Além disso, podemos demonstrar o suporte a 16 bits da BNNSGraph e como, aqui, ele é significativamente mais rápido do que as operações FP32. Veja o código do Swift novamente, agora usando um alias de tipo para especificar a precisão.

    Alterar o alias de tipo altera o gráfico para usar FP16 em vez de FP32. Aqui, FP16 é muito mais rápido do que o equivalente FP32. Para concluir, a BNNSGraphBuilder fornece uma API do Swift prática que permite escrever modelos e gráficos de operações de alto desempenho e eficiência energética. É ótima para casos de uso em tempo real e relacionados à latência. Sua interface de programação amigável permite o uso em várias aplicações. Antes de ir, permita-me sugerir que você acesse nossa página de documentação, onde encontrará muito material de referência sobre a BNNSGraph e a BNNSGraphBuilder. Confira também o vídeo do ano passado, onde falo sobre a API baseada em arquivo e o uso da BNNSGraph em C.

    Muito obrigado por assistir.

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

Developer Footer

  • Vídeos
  • WWDC25
  • Novidades do BNNSGraph
  • 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