View in English

  • 打开菜单 关闭菜单
  • Apple Developer
搜索
关闭搜索
  • Apple Developer
  • 新闻
  • 探索
  • 设计
  • 开发
  • 分发
  • 支持
  • 账户
在“”范围内搜索。

快捷链接

5 快捷链接

视频

打开菜单 关闭菜单
  • 专题
  • 相关主题
  • 所有视频
  • 关于

更多视频

  • 简介
  • 概要
  • 转写文稿
  • 代码
  • BNNS Graph 的新功能

    借助 BNNS Graph Builder API,开发者现在可以使用熟悉的 Swift 语言来编写操作图,从而生成预处理和后处理例程以及小型机器学习模型。BNNS 可在执行前先编译图形,并支持那些具有实时性和延迟敏感性的应用场景,例如音频处理。在本次讲座中,我们将重温去年的比特失真器示例,通过移除对单独 Python 文件的依赖性来简化 Swift 组件,并转而完全用 Swift 实现音频效果。此外,BNNS Graph Builder API 还适用于在将图像数据传递到机器学习模型之前对数据进行预处理。本次讲座还将演示如何裁剪一张带有 Alpha 通道的图像中的透明像素。

    章节

    • 0:00 - 简介
    • 3:12 - BNNSGraph 回顾
    • 6:15 - BNNSGraphBuilder
    • 16:58 - 使用 BNNSGraphBuilder

    资源

    • BNNS
    • Supporting real-time ML inference on the CPU
    • vImage.PixelBuffer
      • 高清视频
      • 标清视频

    相关视频

    WWDC25

    • 探索 Apple 平台上的机器学习和 AI 框架

    WWDC24

    • 在 CPU 上助力实现实时 ML 推理
  • 搜索此视频…

    大家好 我叫 Simon Gladman 来自 Apple 的Vector and Numerics Group 我们团队提供了一套库 用于在 CPU 上实现高性能、高能效的计算 这些库支持 (包括但不限于) 图像与信号处理、线性代数 以及我今天要讲的机器学习 基本神经网络子程序 (BNNS)是我们的机器学习库 它能让你在 App 中添加基于 CPU 的推理功能 非常适合实时和低延迟的用例 例如音频处理 你可以用它实现以下功能: 进行音频分离以分离或消除人声 根据内容将音频划分成不同的片段

    或是应用音色转换让一种乐器发出另一种乐器的声音

    不过 BNNS 也非常适合用于处理其他类型的数据 例如图像数据 所以如果 App 需要高性能推理BNNS 会是不错的选择

    去年我们推出了BNNSGraph这是一个令人兴奋的新 API 让 BNNS 运行更快、更节能并且远比以往更加易于使用 记得我们当时演示 BNNSGraph 时 创建了这个 BitCrusher 音频单元

    展示了在 Logic Pro 和库乐队中添加自定义音效 有多简单

    今年我们给 BNNSGraph升级了编程接口 现在可以利用 Swift 的强大功能来构建小型推理模型 以及预处理和后处理的操作图 今天我要介绍BNNSGraph 的新功能 BNNSGraphBuilder 今天的讲座会先快速回顾一下 BNNSGraph 它是什么 怎么优化机器学习和 AI 模型 以及如何集成到项目中 然后介绍 BNNSGraphBuilder 概述实现它所需的极简工作流程 并在介绍过程中演示如何编写一个简单的图 介绍 BNNSGraphBuilder 后我将进行三个演示 首先是使用 Graph Builder预处理图像 然后是使用新的 API对 ML 模型生成的数据进行后处理 最后我将更新去年的 Bitcrusher 演示 用 Swift 构建操作图 现在话不多说我们开始吧 我们建议在音频和其他对延迟敏感的用例中使用 BNNSGraph 因为它能让你控制内存分配和多线程处理 这两者都可能引发上下文切换到内核代码 从而导致无法实现 实时处理所需的及时性 在去年之前 构建基于 BNNS 的网络需要使用 卷积、矩阵乘法或激活函数等离散层 如果你想使用 BNNS来实现已有的模型 则必须将每个层编码为 BNNS 原语 并为所有中间张量编写代码 BNNSGraph 会将包含多个层及层间数据流的完整图 作为单个图对象处理

    这意味着你无需 为每个单独的层编写代码 而且你和用户都将受益于 更快的性能和更高的能效

    由于 BNNSGraph 能够理解整个模型 因此它能够完成很多 以前无法完成的优化 这些优化可减少执行时间、内存使用和能耗 更棒的是 优化都是自动进行的 这是一个模型的一小部分让我们用它来探讨其中的一些优化 第一项优化是数学变换 例如对切片运算进行重新排序 使 BNNS 只需计算切片中的元素子集 第二项优化是层融合 例如将卷积层 和激活层融合为单个操作 第三项优化是拷贝省略通过引用数据子集 避免拷贝切片中的数据 此外通过确保张量尽可能共享内存BNNSGraph 可优化内存使用 同时消除不必要的资源分配 最后 BNNSGraph 的权重重打包优化 可以重新打包权重以提供更好的缓存局部性 你无需编写任何代码就能从这些优化中获益 优化会自动实现 要使用去年推出的基于文件的 API 创建图 首先需要一个 CoreML 软件包 Xcode 会自动将这个软件包编译成 mlmodelc 文件 然后你需要编写代码根据 mlmodelc 文件构建图 最后一步是创建上下文来封装图 这也是执行推理的上下文 这种方法仍是将现有 PyTorch 模型集成到项目中的 最佳方式 但是对于小型模型或操作图 你可能需要更直接的工作流程 如果能在 Swift 中定义图的各个元素会怎样? 今年我们推出了一个新的 API 来实现这一点 BNNSGraphBuilder

    新的 BNNSGraphBuilder API使你能够 使用熟悉的 Swift 语言编写操作图 你可以直接在 Swift 中编写 预处理和后处理例程或小型机器学习模型 只需一个函数调用即可从这个 Swift 代码创建上下文 无需中间步骤 在 Swift 中与其他代码内联编写图 带来一些直接的好处 使用熟悉的语言并且在编译 Swift 项目时 自动进行类型检查 另外还可以在 Swift和图之间共享值 这些值在运行时生成图之前就已知 例如如果在初始化图之前的运行时 知道张量的形状 就可以将形状直接传递到图代码中 利用静态尺寸比灵活尺寸更好的性能 BNNSGraphBuilder 还允许查询中间张量的 形状和数据类型等属性这有助于调试图 类型化张量支持Xcode 自动补全 并减少运行时错误的可能性 我们来看看新的语法、类型检查、新的方法和运算符 以及如何查询中间张量 这些都是以前没有的功能 今年我们为 BNNSGraph 对象添加了一种新的类型方法 名为 makeContext 正是这个新方法将 Swift 代码转换为可重用的上下文 用于执行图 通常只在 App 启用时创建一次上下文 然后 App 在需要时执行上下文 并受益于从整体上处理图 带来的优化 现在通过一些实时编码来看看makeContext 的实际应用 这个方法接受一个闭包这个闭包用于定义 图的一系列操作

    图通常接受一个或多个参数 这里使用闭包的 builder参数来指定两个参数 x 和 y

    这两个参数均为 8 元素的浮点值向量 在这个示例中图对两个输入参数 执行逐元素乘法

    并将结果写入名为 product 的输出参数 图还计算 product 的平均值 并将这个值写入名为 mean 的第二个输出参数 最后 代码返回这两个输出参数

    为了帮助调试可以打印 BNNSGraphBuilder 生成的 中间张量和输出张量的详细信息

    在这个示例中 我打印了 mean 的形状以确保它的形状为 1 即仅包含一个元素 创建上下文后 可以查询它以获取参数和参数位置 这里 代码查询上下文的参数名称 并创建 表示图输出和输入的张量数组 BNNSGraph 对参数名称进行排序 使输出在前 输入在后

    BNNSGraphBuilder API 提供了一组丰富的函数 用于从不同数据源初始化参数 并拷贝或引用数据 在这个示例中代码从 Swift 数组初始化输入

    分配输入和输出参数后 调用 execute 函数将执行图 并将结果填充到两个输出中

    我们可以通过运行函数来打印结果

    不过不仅限于乘法和计算平均值 让我们快速了解一下BNNSGraphBuilder 并探索新 API 提供的一些其他操作 这只是 BNNSGraphBuilder所包含原语的 一小部分 例如 矩阵乘法、卷积和归约操作、 收集和分散操作、 以及填充、重塑和转置等操作 我们的新 API 支持许多操作作为简单的 Swift 运算符 因此我们有算术运算符包括乘法、 加法、减法和除法 还有逐元素比较运算符包括等于、 小于和大于 此外还有逐元素逻辑运算符 好了 以上就是对 API 本身的简要介绍 让我们深入了解一下Graph Builder API 的两个功能 Swift 开发者会非常熟悉首先是强类型 强类型有助于在编译时捕获错误 否则错误可能在运行时发生 BNNSGraphBuilder API可确保张量的数据类型 对于给定操作是正确的 让我们来看看它的实际应用 在这个示例中图返回浮点基值 提升到整数指数的逐元素结果

    它能够通过将整数指数转换为 FP16 来执行这个计算 如果不进行转换makeContext 方法 将无法编译 图还通过将 mask0 中值小于 mask1 对应值的元素归零 来屏蔽结果 由于比较生成的张量元素是布尔值 图会执行另一个转换以将结果与条件相乘 同样 如果没有转换操作makeContext 方法将无法编译 最好在编译时捕获这类错误 而不是在用户开始使用 App 之后 以上就是强类型的实际应用 现在我们来看看BNNSGraphBuilder 的切片处理方式 也就是张量的部分选择操作 这也是 Swift 开发者非常熟悉的功能

    切片实际上是张量特定部分的窗口 例如 切片可以是矩阵的单列或单行 BNNSGraph的优势在于 将切片视为对现有数据的引用 无需拷贝数据或分配更多内存 张量切片是常见操作 新的 Graph Builder API允许将张量切片指定为 操作的源或目标 例如 你可能需要在将图像传递给机器学习模型之前 将图像裁剪到感兴趣区域 我们来看看使用 Graph Builder API 从这张松鼠享用午餐的照片中选择正方形区域 有多简单 我们将定义两个 vImage 像素缓冲区像素缓冲区存储图像的像素数据、 尺寸、位深和通道数 第一个像素缓冲区 source包含松鼠的照片 第二个像素缓冲区 destination将包含正方形裁剪区域 每个像素缓冲区有三个通道红、绿、蓝 每个通道为32 位浮点格式 如需进一步了解 vImage 像素缓冲区可以查看我们的 vImage 文档 水平和垂直边距可确保640 x 640 的裁剪区域 位于源图像中心 这是一个使用切片操作执行裁剪的图 首先将 source 定义为参数 并将高度和宽度指定为灵活尺寸 传递负值(这里为 -1) 告知 BNNSGraph 这些维度可以是任意值 形状中的最后一个值 3表示图像包含三个通道: 红、绿和蓝 BNNSGraphBuilder API 使用Swift 下标来执行切片操作 在这个示例中 代码使用新的 SliceRange 结构 垂直和水平维度的起始索引 是对应的边距值 将结束索引设置为负边距值表示结束索引 是这个维度的结束值减去边距

    在这个示例中我们不想沿通道维度进行裁剪 代码指定 fillAll 以确保包含所有三个通道

    今年我们还为vImage 像素缓冲区引入了新方法 withBNNSTensor 方法允许创建临时 BNNSTensor 它与像素缓冲区共享内存和属性例如尺寸和通道数 正如这里的代码所示新方法使图像进出图的传递 变得非常简单 由于内存是共享的因此无需拷贝或分配 这意味着处理图像时性能出色 execute 函数方法返回后destination 像素缓冲区 包含裁剪后的图像 我们可以通过创建结果的 Core Graphics 图像来 仔细确认已完整裁切松鼠图像 除了新的 Graph Builder切片范围结构 张量切片 API 支持所有 Swift 范围类型 因此 无论切片需求如何我们都能满足 以上就是对切片的简要概述 现在我们已经了解了BNNSGraphBuilder 和它的一些功能 下面我们来深入探讨一些用例 BNNSGraphBuilder 是用于构建操作图的出色 API 可用于预处理传递给 ML 模型的数据或后处理从 ML 模型接收的数据 一种预处理技术是图像阈值处理 即将连续色调图像转换为仅包含黑色和白色像素的 二值图像 让我们来看看如何使用Graph Builder 实现这一功能

    在开始编写图之前 我们将再次定义一些 vImage 像素缓冲区 第一个存储源图像第二个是 接收阈值处理图像的目标 这两个像素缓冲区都是单通道、16 位浮点格式 图中的第一步是 定义表示输入图像的 source 代码为图像尺寸传递负值 指定图支持任意图像尺寸 但会指定与像素缓冲区匹配的 FP16 数据类型 计算连续灰度像素的平均值 只需调用mean 方法 逐元素大于运算符 会在阈值处理张量中为大于平均像素值的对应像素 填充 1否则填充 0 最后 图将布尔值 1 和 0 转换为 destination 像素缓冲区的位深 我们将再次使用 withBNNSTensor方法将两个图像缓冲区 传递到上下文中并执行函数以生成阈值处理图像

    将这张飞行中鹈鹕的连续色调灰度图像 传递到图后 我们得到这个阈值处理图像其中所有像素均为黑色或白色 BNNSGraphBuilder API的另一个用例是 对机器学习模型的结果进行后处理

    例如 假设我们想对 ML 模型的结果应用 softmax 函数 然后执行 top-k 运算 这里 后处理函数会动态创建一个小图 图基于函数的输入张量声明输入参数 然后对输入参数应用 softmax 运算 然后计算它的 top-k值和索引 请注意 代码传递给top-k 函数的 k 参数实际上 定义在 makeContext 闭包之外 最后 图返回top-k 结果 定义图后 函数声明存储结果的张量 并将输出和输入张量传递给上下文 最后 makeArray 方法将top-k 值和索引 转换为 Swift 数组并返回 这些是使用 Graph Builder API对数据进行预处理 和后处理的示例 现在 让我们来看看去年的 BitCrusher 演示 并更新 Swift 部分以使用BNNSGraphBuilder 这段代码演示了 BNNSGraph 如何让添加基于 ML 的音效变得非常简单

    去年 我们通过将 BNNSGraph 整合到“Audio Unit Extension”App 中 来演示 BNNSGraph 这个 App 向音频信号添加实时 BitCrusher 效果 这个演示使用 Swift对正弦波应用相同效果 以在用户界面中显示效果的可视化表示 例如 给定这个正弦波图能够降低分辨率 为声音添加失真可视化表示 将正弦波显示为离散的阶梯序列而非连续波 我们还加入了饱和增益以增加声音的丰富度 去年的演示使用了基于 PyTorch 代码的 CoreML 软件包 因此 我们来将去年的代码与使用新的 Graph Builder API 在 Swift 中编写的相同功能进行比较 左侧是 PyTorch 代码右侧是新的 Swift 代码 一个直接区别是 Swift 使用 let 和 var 定义中间张量 因此可以自由决定这些张量是不可变还是可变的 无需任何额外导入即可访问 所有可用操作 此外在这个示例中 tanh 等操作是张量的方法而非自由函数

    逐元素算术运算符 在这两种不同方法中是相同的

    另一个区别是makeContext 闭包可以返回 多个输出张量因此始终返回一个数组 Swift Graph Builder 允许在 makeContext 闭包内 定义输入参数 如果已有 PyTorch 模型我们仍建议使用 这个代码和现有基于文件的 API 但是 对于新项目新的 Graph Builder API 允许使用 Swift 编写图正如这个比较所示 它的结构与使用 PyTorch 编写的相同操作集相似 此外 我们可以演示BNNSGraph 的 16 位支持 以及在这个示例中它的速度如何明显快于 FP32 操作 这也是我们的 Swift 代码但这次使用类型别名 指定精度

    更改类型别名会使图使用 FP16 而不是 FP32 在这个示例中 FP16比等效的 FP32 快得多 总而言之 BNNSGraphBuilder 提供了一个易于使用的 Swift API 允许编写高性能、高能效的模型 和操作图 它非常适合实时和对延迟敏感的用例 而用户友好的编程接口 意味着它可用于广泛的应用场景 最后建议大家 查看我们的文档页面那里有很多 关于 BNNSGraph 和BNNSGraphBuilder 的参考资料 另外 请务必观看我们去年的视频 其中介绍了基于文件的 API 以及在 C 语言中使用 BNNSGraph 的方法

    非常感谢大家的观看祝大家生活愉快

    • 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 - 简介
    • Apple Vector and Numerics Group 开发了 BNNS,这是一个机器学习库,用于 App 中基于 CPU 的推理,对于音频和图像的实时处理特别有用。 去年推出的 BNNSGraph 提高了速度、效率和易用性。现在又添加了 BNNSGraphBuilder,可以基于 Swift 创建用于预处理和后处理的小模型和图。这个讲座通过图像预处理、数据后处理和更新 Bitcrusher 音频单元采样展示了这一点。

    • 3:12 - BNNSGraph 回顾
    • 对于要求及时完成实时处理的音频和低延迟任务,建议使用 BNNSGraph,因为它允许内存和多线程控制,可提高实时性能。 以前,BNNS 需要手动对每个层进行编码,但现在 BNNSGraph 将整个图作为对象,通过结合数学变换、层融合、拷贝省略和张量内存共享来优化性能和能效。 BNNSGraph 提供两个主要工作流程:使用 CoreML 软件包和 Xcode 编译,或者使用 BNNSGraphBuilder 直接在 Swift 中为较小的模型定义图。

    • 6:15 - BNNSGraphBuilder
    • 借助名为 BNNSGraphBuilder 的新 API,开发者可以直接在 Swift 中构建操作图。这样就可以在 Swift 代码中创建预处理和后处理例程以及小型机器学习模型。 这样的好处包括能使用熟悉的语言、可在编译期间进行类型检查以及能够在 Swift 和图之间共享运行时值,从而提高性能。这个 API 还提供调试功能,例如查询中间张量的形状和数据类型等属性,并且还可启用 Xcode 自动补全功能。 一种新的类型方法“makeContext”将 Swift 代码转换为可重用的上下文以执行图。这个上下文在 App 启动期间创建一次,然后可以多次执行,带来整体图优化的好处。这个 API 提供了广泛的数学和逻辑运算,并支持常见的神经网络基本运算,如矩阵乘法、卷积和归约操作。 Swift 中的 BNNSGraphBuilder API 利用强类型在编译时捕获错误,从而确保张量运算的数据类型正确性。这个功能体现为,可以自动转换数据类型 (例如将整数转换为 FP16),这可以防止编译错误并提高 App 的可靠性。 这个 API 还将张量切片视为对现有数据的引用,从而优化内存的使用。张量切片使用 Swift 下标和新的 SliceRange 结构来执行,因此对 Swift 开发者来说非常直观。这样就能进行高效的操作,例如图像裁剪,就如同示例中展示的那样,从一张松鼠的照片中裁剪出一个方形区域,使用 vImage 像素缓冲区和“withBNNSTensor”方法共享内存,从而提高性能。

    • 16:58 - 使用 BNNSGraphBuilder
    • BNNSGraphBuilder 是 Swift 中一个强大的 API,可用来构建操作图,以便在利用音频和图像的机器学习应用程序中实现高效的数据预处理和后处理。 对于预处理,请使用 API 对图像进行阈值处理,将连续色调图像转换为二值图像。你还可以将它用于后处理任务,例如将 softmax 函数和 topK 运算应用于机器学习模型的结果。 我们还将这个 API 应用于音频效果,展示了它丰富的功能。你可以用它创建实时音频效果,例如比特失真,并且与 32 位精度相比,使用 16 位精度可以让性能显著提高。 BNNSGraphBuilder 的编程界面直观易用,适合各种应用程序,从需要实时处理和对延迟极其敏感的用例,到常规机器学习任务,都可胜任。

Developer Footer

  • 视频
  • WWDC25
  • BNNS Graph 的新功能
  • 打开菜单 关闭菜单
    • iOS
    • iPadOS
    • macOS
    • Apple tvOS
    • visionOS
    • watchOS
    打开菜单 关闭菜单
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    打开菜单 关闭菜单
    • 辅助功能
    • 配件
    • App 扩展
    • App Store
    • 音频与视频 (英文)
    • 增强现实
    • 设计
    • 分发
    • 教育
    • 字体 (英文)
    • 游戏
    • 健康与健身
    • App 内购买项目
    • 本地化
    • 地图与位置
    • 机器学习与 AI
    • 开源资源 (英文)
    • 安全性
    • Safari 浏览器与网页 (英文)
    打开菜单 关闭菜单
    • 完整文档 (英文)
    • 部分主题文档 (简体中文)
    • 教程
    • 下载 (英文)
    • 论坛 (英文)
    • 视频
    打开菜单 关闭菜单
    • 支持文档
    • 联系我们
    • 错误报告
    • 系统状态 (英文)
    打开菜单 关闭菜单
    • Apple 开发者
    • App Store Connect
    • 证书、标识符和描述文件 (英文)
    • 反馈助理
    打开菜单 关闭菜单
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program (英文)
    • News Partner Program (英文)
    • Video Partner Program (英文)
    • 安全赏金计划 (英文)
    • Security Research Device Program (英文)
    打开菜单 关闭菜单
    • 与 Apple 会面交流
    • Apple Developer Center
    • App Store 大奖 (英文)
    • Apple 设计大奖
    • Apple Developer Academies (英文)
    • WWDC
    获取 Apple Developer App。
    版权所有 © 2025 Apple Inc. 保留所有权利。
    使用条款 隐私政策 协议和准则