View in English

  • 메뉴 열기 메뉴 닫기
  • Apple Developer
검색
검색 닫기
  • Apple Developer
  • 뉴스
  • 둘러보기
  • 디자인
  • 개발
  • 배포
  • 지원
  • 계정
페이지에서만 검색

빠른 링크

5 빠른 링크

비디오

메뉴 열기 메뉴 닫기
  • 컬렉션
  • 주제
  • 전체 비디오
  • 소개

WWDC25 컬렉션으로 돌아가기

스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.

  • 소개
  • 요약
  • 자막 전문
  • 코드
  • BNNS Graph의 새로운 기능

    이제 개발자는 BNNS Graph Builder API를 통해 익숙한 Swift 언어를 사용하여 작업 그래프를 작성함으로써 사전 및 사후 처리 루틴 및 소규모 머신 러닝 모델을 생성할 수 있습니다. BNNS는 실행하기 전에 그래프를 컴파일하고 오디오 처리와 같이 실시간 및 지연에 민감한 사용 사례를 지원합니다. 이 세션에서는 작년 비트 크러셔 예제를 다시 검토하고, 별도의 Python 파일에 의존하지 않고 Swift 구성 요소를 단순화하며, 대신 Swift의 오디오 효과를 온전히 구현합니다. BNNS Graph Builder API는 또한 이미지 데이터를 머신 러닝 모델에 전달하기 전에 사전 처리하는 데에도 적합합니다. 이 세션에는 알파 채널로 이미지에서 투명한 픽셀을 클리핑하는 방법을 보여주는 데모도 포함되어 있습니다.

    챕터

    • 0:00 - Introduction
    • 3:12 - Recapping BNNSGraph
    • 6:15 - BNNSGraphBuilder
    • 16:58 - Using BNNSGraphBuilder

    리소스

    • BNNS
    • Supporting real-time ML inference on the CPU
    • vImage.PixelBuffer
      • HD 비디오
      • SD 비디오

    관련 비디오

    WWDC25

    • Apple 플랫폼의 머신 러닝 및 AI 프레임워크 살펴보기

    WWDC24

    • CPU에서 실시간 ML 추론 지원하기
  • 비디오 검색…

    Hi, my name is Simon Gladman, and I work for the Vector and Numerics Group here at Apple. Our group provides a suite of libraries for high-performance, energy-efficient computation on the CPU. These libraries support, among other things, image and signal processing, linear algebra, and what I’ll be talking about today, machine learning.

    Basic Neural Network Subroutines, or BNNS, is our machine learning library. It allows you to add CPU-based inference to your app, and is perfectly suited to real-time and low-latency use cases, such as audio processing. You might use it to implement functionalities such as: separating audio to isolate or remove vocals, segmenting audio into different regions based on content, or applying timbre transfer to make one instrument sound like another.

    But BNNS is also perfectly suited to working with other data, such as images. So if your app requires high performance inference, BNNS is for you.

    Last year we introduced BNNSGraph, an exciting new API that made BNNS faster, more energy efficient and far far easier to work with. And if you recall we demonstrated BNNSGraph by creating this Bitcrusher audio unit that shows just how easy it is to add your own effects to Logic Pro and Garage Band.

    This year, we’re giving BNNSGraph an even better programming interface that allows you to leverage the power of Swift to create small models for inference and graphs of operations for pre- and post-processing. Today, I’ll be talking about our new addition to BNNSGraph, BNNSGraphBuilder. I’ll start today’s session with a quick recap of BNNSGraph. What it is, how it can optimize machine learning and AI models and how you can adopt it. Then I’ll introduce BNNSGraphBuilder. I’ll summarize the super easy workflow you need to implement it and as part of this introduction, I’ll demonstrate how to write a simple graph. After introducing BNNSGraphBuilder, I run through three demonstrations. Firstly preprocessing an image using the Graph Builder. Then using the new API to postprocess data generated by an ML model. And finally I’ll update last year’s Bitcrusher demonstration to build the graph of operations in Swift. And now, without further ado, let’s get started. We recommend BNNSGraph for audio and other latency-sensitive use cases because it gives you control over allocating memory and multithreading. Both of these may incur a context switch into kernel code and lead to defeat of real-time deadlines.

    Before last year, you would build BNNS based networks using discrete layers such as convolution, matrix multiplication or activation.

    If you wanted to use BNNS to implement an existing model, you’d have to code each layer as a BNNS primitive and write the code for all of the intermediate tensors. BNNSGraph takes an entire graph that consists of multiple layers and the data flow between those layers, and consumes it as a single graph object.

    For you, this means you don’t have to write the code for each individual layer. Furthermore, you and your users will benefit from faster performance and better energy efficiency.

    Because BNNSGraph is aware of the entire model, it is able to perform a number of optimizations that were not possible before. These optimizations reduce execution time, memory usage and energy cost. And even better, they come for free. Let’s use this small section of a model to explore some of these optimizations. One of the optimizations is a mathematical transformation. For example, reordering a slice operation so that BNNS only needs to compute the subset of elements in that slice. Another optimization is layer fusion. For example, fusing a convolution layer and an activation layer into a single operation. And copy elision avoids copying the data in a slice by referencing that subset of data instead. And by ensuring tensors share memory where possible, BNNSGraph optimizes memory usage and eliminates unnecessary allocations.

    Finally, BNNSGraph’s weight repacking optimizations can repack weights to provide better cache locality. You don’t need to write any code to benefit from these optimizations. They happen just like that. To create a graph using the file-based API that we introduced last year, you start off with a CoreML Package. Xcode automatically compiles the package to an mlmodelc file and then you write the code that builds a graph from the mlmodelc file. The last step is to create a context to wrap the graph and it’s that context that performs inference. This approach is still the best way to integrate existing PyTorch models into your project. However, for small models or graphs of operations, you might benefit from a more immediate workflow. What if you could define the individual elements of your graph in Swift? Well, this year we’re introducing a new API that does just that BNNSGraphBuilder.

    The new BNNSGraphBuilder API enables you to write graphs of operations using the familiar Swift language. You can write pre- and post-processing routines or small machine learning models directly in Swift. You create a context from that Swift code with a single function call and no intermediate steps. Writing your graphs in Swift and inline with your other code brings some immediate benefits. You use a familiar language and your graph is type-checked when your Swift project is compiled. You can also share values between Swift and your graph that are known at runtime but before you generate the graph. For example, if you know a tensor’s shape at runtime before you initialise the graph, you can pass the shape directly into the graph code, and benefit from the better performance of static sizes over flexible sizes.

    BNNSGraphBuilder also allows you to query intermediate tensors for properties such as shape and data type, and this helps you to debug graphs. And type tensors allows Xcode to autocomplete and reduces the chance of runtime errors. Let’s look at the new syntax, type checking, new methods and operators, and how to query intermediate tensors, all features that were not previously available.

    This year we’ve added a new type method to the BNNSGraph object, named makeContext. It is this new method that converts your Swift code to a reusable context that you use to execute your graph. You only make the context once, typically while your app is starting up. Then your app executes the context when required, and it benefits from the optimizations that treating the graph holistically brings. Now for some live coding to see makeContext in action.

    This method accepts a closure, and it’s that closure that you use to define the series of operations that constitute your graph.

    Your graph will typically accept one or more arguments. Here, I’ll use the closure’s builder parameter to specify the two arguments, x and y.

    The parameters are both eight element vectors of float values. In this example the graph performs an element-wise multiplication on the two input arguments, and writes the result to the output argument that I have named product. My graph also calculates the mean value of the product and writes that value to a second output argument I have named mean, and finally, the code returns those two output arguments.

    To aid debugging, we can print out details of the intermediate and output tensors that BNNSGraphBuilder generates.

    In this example, I’ve printed the shape of the mean to ensure it’s got a shape of one, that is, it only contains one element. Once the context is created, you can query it to derive the arguments and their positions. Here, the code queries the context’s argument names and creates an array of tensors that represent the graph’s outputs and inputs. BNNSGraph orders argument names so that the outputs are first and the inputs follow.

    The BNNSGraphBuilder API provides a rich set of functions for initializing arguments from different data sources, and either copying or referencing data. In this example, the code initializes the inputs from Swift arrays.

    With the input and output arguments allocated, calling execute function executes the graph and populates the two outputs with the results.

    And we can print out the results by running the function.

    We’re not limited to just multiplying and calculating averages though. Let’s take a quick tour around BNNSGraphBuilder and explore some of the other operations that the new API provides. Here’s just a tiny selection of the primitives included in BNNSGraphBuilder. For example, matrix multiplication and convolution, reduction operations, gather and scatter operations, and operations such as padding, reshape and transpose. Our new API supports many operations as simple, Swift operators. So we have arithmetic operators, multiplication, addition, subtraction and division. We also have element-wise comparison operators including equality, less than, and greater than. And to complete the set, we have element-wise logical operators. OK, that’s a quick introduction to the API itself. Let’s take a deep dive into two features of the GraphBuilder API that will be very familiar Swift developers, starting with strong typing.

    Strong typing helps you catch errors at compile time that otherwise may happen at runtime. The BNNSGraphBuilder API ensures that the data type for tensors is correct for a given operation. Let's look at this in action. In this example, the graph returns the element-wise results of floating-point base values raised to the power of integer exponents.

    It’s able to perform this calculation by casting the integer exponents to FP16.

    Attempting to perform this without the cast would prevent the make context method from compiling. The graph also masks the result by zeroing out elements where the values in mask 0 are less than the corresponding values in mask 1.

    Because the tensor elements that the comparison generates are Boolean, the graph performs another cast in order to multiply the results by the condition. Once again, without the cast operation, the make context method wouldn’t compile. And it’s always best to catch this type of error at compile time and before your app is in the hands of your users. That's strong typing in action. Let’s now take a look at BNNSGraphBuilder’s approach to slicing, that is selecting parts with tensor. And this too will be very familiar to Swift developers.

    A slice is effectively a window onto a specific part of a tensor. For example, a slice may be a single column or row of a matrix. The great thing about BNNSGraph is that it treats slices as references to existing data, without the overhead of copying that data or allocating more memory. Slicing tensors is a common operation. Our new GraphBuilder API allows you to specify slices of a tensor as the source or destination operations.

    For example, you might want to crop an image to a region of interest before passing it to a machine learning model. Let’s look at how easy it is to use the GraphBuilder API to select a square region from the centre of this photograph of a squirrel enjoying its lunch.

    We’ll define two vImage pixel buffers. Pixel buffers store in images pixel data, dimensions, bit depth and number of channels. The first buffer, source, contains a photograph of the squirrel. The second pixel buffer, Destination, will contain the square crop. Each pixel buffer is three channels, red, green and blue, and each channel is 32-bit floating point format. You can learn more about vImage pixel buffers in our vImage documentation.

    The horizontal and vertical margins ensure that the 640 by 640 crop is centered on the source image. And here’s a graph that uses the slice operation to perform the crop. First, we’ll define the source as an argument and specify the height and width as flexible sizes.

    Passing a negative value, minus 1 in this case, tells BNNSGraph that those dimensions might be any value. The final value in the shape 3 specifies that the image contains three channels: red, green and blue.

    The BNNSGraphBuilder API uses Swift subscripts to perform a slice operation. In this example, the code uses the new SliceRange structure. The start indices for both the vertical and horizontal dimensions are the corresponding margin values. Setting the end indices as the negative margin value indicates that the end index is the end value of that dimension minus the margin.

    In this example we don’t want to crop along the channel dimension, and the code specifies fillAll to ensure that we include all three channels.

    This year we’re also introducing a new method for vImage pixel buffers. The withBNNSTensor method allows you to create a temporary BNNSTensor that shares memory and properties such as size and channel count with the pixel buffer. As the code here demonstrates, the new method makes passing images to and from your graph super easy. And because memory is shared, there’s no copying or allocating, and that means you get great performance when working with images.

    After the execute function method returns, the destination pixel buffer contains a cropped image. And we can double check our squirrel is safely cropped by creating a core graphics image of the result. In addition to the new GraphBuilder slice range structure, the tensor slicing API supports all the Swift range types. So, whatever your slicing needs, we have you covered. And that is a brief overview of slicing.

    Now we’ve taken a look at BNNSGraphBuilder and some of its features, let's dive into some use cases.

    BNNSGraphBuilder is a great API to construct graphs of operations for pre- and post-processing data that you pass to or receive from ML models. One pre-processing technique is to threshold an image. That is to convert a continuous tone image to a binary image that contains just black or white pixels. Let’s take a look at how we can implement this using the GraphBuilder.

    Before we start writing the graph, we’ll again define some vImage pixel buffers. The first stores the source image, and the second is the destination that receives the thresholded image. Both of these pixel buffers are single channel, 16-bit floating point format.

    The first step in the graph is to define the source, which represents the input image. The code passes negative values for the image size to specify the graph supports any image size, but does however specify the FP16 data type that matches the pixel buffers. Calculating the average value of the continuous grayscale pixels is simply a matter of calling the mean method. The element wise is greater than operator populates the thresholded tensor with ones for corresponding pixels of the value greater than the average pixel value, and zeros otherwise.

    And finally, the graph casts the Boolean ones and zeros to the bit-depth of the destination pixel buffer. We’ll use the withBNNSTensor method again to pass the two image buffers to the context and execute the function to generate the thresholded image.

    After passing this continuous-toned grayscale image of a pair of pelicans in flight to the graph We get this thresholded image where all the pixels are either black or white.

    Another use case for the BNNSGraphBuilder API is post-processing the results of a machine learning model.

    For example, let’s say we want to apply a softmax function followed by a topK operation to the results of an ML model. Here, the post-process function creates a small graph on the fly. The graph declares an input argument based on the function’s input tensor. It then applies a softmax operation to the input argument, and then calculates its topK values and indices. Note that the k parameter the code passes to the topK function is actually defined outside of the make context closure. Lastly, the graph returns the topK results. After defining the graph, the function declares the tensors that store the results, and passes the output and input tensors to the context. Finally, the makeArray method converts the topK values and indices to Swift arrays and returns those.

    There we have some examples of pre- and post-processing data with the GraphBuilder API. Now, let’s take a look at last year’s Bitcrusher demonstration and update the Swift piece to use BNNSGraphBuilder. This code demonstrates how BNNSGraph makes adding ML-based audio effects super easy.

    Last year, we demonstrated BNNSGraph by incorporating it into an Audio Unit extension app. This app added a real-time Bitcrusher effect to an audio signal, and the demonstration used Swift to apply the same effect to a sine wave to display a visual representation of the effect in the user interface. For example, given this sine wave, the graph is able to reduce the resolution to add distortion to the sound, and the visual representation shows the sine wave as a discrete series of steps rather than a continuous wave. We also included saturation gain to add a richness to the sound. Last year’s demonstration used a CoreML package that was based on PyTorch code. So, let’s take last year’s code and compare it against the same functionality written in Swift using the new GraphBuilder API. Here’s the PyTorch on the left and the new Swift code on the right. One immediate difference is that Swift uses let and var to define intermediate tensors, so you’re free to decide if these are immutable or mutable. You don’t need any extra imports to access the full range of available operations. Furthermore, operations such as, in this case tanh, are methods on tensors rather than free functions.

    Element-wise arithmetic operators are the same with the two different approaches.

    Another difference though is that the make context closure can return more than one output tensor, so you always return an array. The Swift GraphBuilder allows you to define input arguments inside the make context closure. If you have an existing PyTorch model, we still recommend using that code and the existing file-based API. But, for new projects, our new GraphBuilder API allows you to write graphs using Swift, and as this comparison demonstrates, with a similar structure to the same set of operations written using PyTorch. Furthermore, we can demonstrate BNNSGraph’s 16-bit support, and how, in this example, it is significantly faster than the FP32 operations. Here’s our Swift code again, but this time using a type alias to specify the precision.

    Changing the type alias changes the graph to use FP16 rather than FP32. In this example, FP16 is much faster than the FP32 equivalent.

    To wrap up, BNNSGraphBuilder provides an easy to use Swift API that allows you to write high-performance and energy-efficient models and graphs-of-operations. It’s great for real-time and latency-sensitive use cases, but its user-friendly programming interface means it can be used for a wide range of applications. Before I go, please allow me to suggest that you head over to our documentation page where you’ll find plenty of reference material that discusses BNNSGraph and BNNSGraphBuilder. Also, be sure to check out our video from last year, where I talk about the file-based API and using BNNSGraph in C.

    Many thanks for your time today and best wishes.

    • 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
    • The Apple Vector and Numerics Group developed BNNS, a machine-learning library for CPU-based inference in apps, particularly useful for real-time audio and image processing. BNNSGraph, introduced last year, enhanced speed, efficiency, and ease of use. Now, BNNSGraphBuilder is added, enabling Swift-based creation of small models and graphs for pre- and post-processing. This is demonstrated with image preprocessing, data post-processing, and updating the Bitcrusher audio unit sample.

    • 3:12 - Recapping BNNSGraph
    • BNNSGraph is recommended for audio and low-latency tasks with real-time deadlines because it allows memory and multithreading control, enhancing real-time performance. Previously, BNNS required coding each layer manually, but now BNNSGraph takes entire graphs as objects, optimizing performance and energy efficiency through combining mathematical transformations, layer fusion, copy elision, and tensor memory sharing. BNNSGraph offers two main workflows: using a CoreML Package and Xcode compilation, or defining the graph directly in Swift for smaller models with BNNSGraphBuilder.

    • 6:15 - BNNSGraphBuilder
    • A new API called BNNSGraphBuilder lets developers construct graphs of operations directly in Swift. This enables the creation of pre- and post-processing routines and small machine learning models within Swift code. The benefits include using a familiar language, type-checking during compilation, and the ability to share runtime values between Swift and the graph, leading to improved performance. The API also provides debugging features, such as querying intermediate tensors for properties like shape and data type, and enables Xcode autocomplete. A new type method, 'makeContext', converts Swift code to a reusable context for graph execution. The context is created once during app startup and can then be executed multiple times, benefiting from wholistic graph optimizations. The API offers a wide range of mathematical and logical operations, as well as support for common neural network primitives like matrix multiplication, convolution, and reduction operations. The BNNSGraphBuilder API in Swift leverages strong typing to catch errors at compile time, ensuring data type correctness for tensor operations. This is demonstrated through automatic casting of data types, such as integers to FP16, which prevents compilation errors and enhances app reliability. The API also treats tensor slices as references to existing data, optimizing memory usage. Slicing tensors is performed using Swift subscripts and the new SliceRange structure, making it intuitive for Swift developers. This allows for efficient operations like image cropping, as shown in an example where a square region is cropped from a photograph of a squirrel using vImage pixel buffers and the 'withBNNSTensor' method, which shares memory for improved performance.

    • 16:58 - Using BNNSGraphBuilder
    • BNNSGraphBuilder is a powerful API in Swift that helps you construct graphs of operations for efficient data pre- and post-processing in machine learning applications utilizing audio and images. For pre-processing, use the API to threshold images, converting continuous-tone images to binary images. You can also use it for post-processing tasks such as applying softmax functions and topK operations to the results of ML models. The API demonstrates its versatility by being applied to audio effects as well. It allows you to create real-time audio effects like bitcrushing, with significant performance improvements when using 16-bit precision compared to 32-bit. BNNSGraphBuilder offers a user-friendly programming interface, making it accessible for a wide range of applications, from real-time and latency-sensitive use cases to general-purpose ML tasks.

Developer Footer

  • 비디오
  • WWDC25
  • BNNS Graph의 새로운 기능
  • 메뉴 열기 메뉴 닫기
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    메뉴 열기 메뉴 닫기
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    메뉴 열기 메뉴 닫기
    • 손쉬운 사용
    • 액세서리
    • 앱 확장 프로그램
    • App Store
    • 오디오 및 비디오(영문)
    • 증강 현실
    • 디자인
    • 배포
    • 교육
    • 서체(영문)
    • 게임
    • 건강 및 피트니스
    • 앱 내 구입
    • 현지화
    • 지도 및 위치
    • 머신 러닝
    • 오픈 소스(영문)
    • 보안
    • Safari 및 웹(영문)
    메뉴 열기 메뉴 닫기
    • 문서(영문)
    • 튜토리얼
    • 다운로드(영문)
    • 포럼(영문)
    • 비디오
    메뉴 열기 메뉴 닫기
    • 지원 문서
    • 문의하기
    • 버그 보고
    • 시스템 상태(영문)
    메뉴 열기 메뉴 닫기
    • Apple Developer
    • App Store Connect
    • 인증서, 식별자 및 프로파일(영문)
    • 피드백 지원
    메뉴 열기 메뉴 닫기
    • 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(영문)
    메뉴 열기 메뉴 닫기
    • Apple과의 만남
    • Apple Developer Center
    • App Store 어워드(영문)
    • Apple 디자인 어워드
    • Apple Developer Academy(영문)
    • WWDC
    Apple Developer 앱 받기
    Copyright © 2025 Apple Inc. 모든 권리 보유.
    약관 개인정보 처리방침 계약 및 지침