vulkan 的显存管理

  • 一个 VkBuffer 对象,多个 offset

    • 使用同一块 VkBuffer 存储中间层的特征数据,不同的 blob 使用不同的offset进行区分 。

VkDeviceMemory结构

  • 可以在内存架构方面做到零拷贝

    • 集成显卡和手机上采用unified内存架构(统一内存架构),这种架构下,GPU可以直接访问CPU上的主存。利用这种架构上的特性,在GPU计算的时候就不用把 内存上的数据 拷贝到 显存 上,计算完成后也不需要将 显存上的数据 拷贝到 内存

不同内存架构的对比.png


对GPU存储布局的优化

1.[c,h,w] 这种布局不太适合在GPU上做IO:[c, h, w] ---> [c/4, h, w, 4]

  • 因为GPU访问和读写显存用的时候更多的是使用 vec4 的类型,ncnn 通过将布局改为**[c/4,h,w,4]**,使得GPU的IO效率得到大幅度提升
  1. 减少内存带宽的需求

    • ncnn 中的 Tensor float数据可以使用半精度
    • 在一些不直接支持 fp16 存储的情况下,ncnn 使用 packHalf2x16unpackHalf2x16 来模拟 fp16fp32 的转换(这两个函数是 GLSL 内置的函数)
  2. 更加方便的维护代码

    • ncnn 中创建了一个 GLSL 的宏。

      所以写代码的时候可以不用管类型上的事,运行时会自动转换为设备支持的 fp32fp16 的对应代码


cpu-gpu 混合推理

  • 模型中有些层,在没有GPU实现的时候,我们需要自动切换到CPU上去做推理。这就涉及到存储布局相互转换

CPU和GPU转换

  • ncnn 提供了一套pipline,使用一套pipline实现端到端的完成 最佳的布局转换。在独显上也倾向使用 fp16 做上传和下载,能用半精度,也会优先使用。

并行推理

  • ncnn 在GPU上实现并行推理的方式。

    • Vulkan的api中同一块gpu会暴露多个队列。

      例如:nvidia的gpu中有8个队列,那么就可以使用多线程的方式同时在8个队列上提交8个任务。

      好处:可以增加GPU的使用率 ,从而提高效率。

11个任务同时在三块gpu上做推理


GLSL->SPIR-V 运行编译

  • 原因:有些驱动需要对 GLSL 或者 SPIR-V 的源代码进行特殊的处理,所以只能采用运行时编译
  • 好处:不需要在离线时编译多个 SPIR-V 的二进制文件,减少二进制文件的体积。

Swiftshader

  • swiftshader项目地址:google在cpu上实现 vulkan驱动 的项目,可以实现在cpu上执行vulkan的代码,可以保证每次代码运行结果都是一致的。

复用 VkPipeline 和相关的 vulkan object

  • 模型加载的时候, 特别是第一次加载模型的时候,由于没有离线的cache和优化的手段, pipeline的编译是一个十分耗时的操作。
  • 有些模型层的参数(kernal size, stride)是一样的。ncnn 在运行时就将 层的参数vulkan对象 的关系记录下来,当遇到具有相同参数层的时候,就可以直接复用之前创建好的 vulkan对象,这样可以显著降低第一次加载模型的耗时。

降低第一次加载模型的耗时