Skip to content

第 5 章:使用 Vulkan 进行推理

1. 什么是 Vulkan

Vulkan 是一种新的图形和计算 API,它是一种低开销、跨平台、高性能的 3D 图形和计算 API。Vulkan 旨在提供更好的性能和更均衡的 CPU/GPU 使用,同时提供更好的多线程能力。

Vulkan 被认为更便捷的方案,并且得到多种供应商和跨平台低层图形 API 的支持。相反,CUDA 仅在 NVIDIA 设备上可用,Metal 仅在 MacOS 和 iOS 上可用,而 OpenCL 则在 Android 7.0+ 中禁被禁用,并且不支持 iOS。

2. NCNN 如何使用 Vulkan

Vulkan 的支持性:

支持性WindowsLinuxAndroidMaciOS
Intel
AMD
Nvidia
Qcom
Apple
ARM

2.1 安装 Vulkan 支持

在使用之前,我们需要开启 Vulkan 的支持。在你的系统上安装 Vulkan 支持,一些系统已经内置支持了 Vulkan:

bash
sudo dnf install vulkan-devel

2.2 在项目中使用 Vulkan

在编译项目时指定参数来开启 Vulkan 功能:

bash
cmake -DNCNN_VULKAN=ON ..

为了支持 Vulkan 推理,还需要在 Net 对象中启用 Vulkan:

cpp
ncnn::Net net;
net.opt.use_vulkan_compute = 1;

指定使用 Vulkan 的网络运行在哪个 GPU 上:

cpp
// 获取 GPU 数量
int gpu_count = ncnn::get_gpu_count();

// 设置 Vulkan 设备
net.set_vulkan_device(0); // 将使用 device-0

2.3 使用合适的分配器

cpp
// 获取 Vulkan 的分配器
ncnn::VkAllocator* blob_vkallocator = vkdev.acquire_blob_allocator();
ncnn::VkAllocator* staging_vkallocator = vkdev.acquire_blob_allocator();

net.opt.blob_vkallocator = blob_vkallocator;
net.opt.workspace_vkallocator = blob_vkallocator;
net.opt.staging_vkallocator = staging_vkallocator;

// 推理
// ...

// 推理后
vkdev.reclaim_blob_allocator(blob_vkallocator);
vkdev.reclaim_staging_allocator(staging_vkallocator);

2.4 使用零复制的一致的内存设备

cpp
ncnn::VkMat blob_gpu;
ncnn::Mat mapped = blob_gpu.mapped();

// 下面可以直接使用 mapped.data

2.5 使用 CPU/GPU 混合推理

cpp
ncnn::Extractor ex_cpu = net.create_extractor();
ncnn::Extractor ex_gpu = net.create_extractor();
ex_cpu.set_vulkan_compute(false);
ex_gpu.set_vulkan_compute(true);

#pragma omp parallel sections
{
    #pragma omp section
    {
        ex_cpu.input();
        ex_cpu.extract();
    }
    #pragma omp section
    {
        ex_gpu.input();
        ex_gpu.extract();
    }
}

3. 零复制的 GPU 链式推理

cpp
ncnn::Extractor ex1 = net1.create_extractor();
ncnn::Extractor ex2 = net2.create_extractor();

ncnn::VkCompute cmd(&vkdev);

ncnn::VkMat conv1;
ncnn::VkMat conv2;
ncnn::VkMat conv3;

ex1.input("conv1", conv1);
ex1.extract("conv2", conv2, cmd);

ex2.input("conv2", conv2);
ex2.extract("conv3", conv3, cmd);

cmd.submit();
cmd.wait();

4. 批量推理

cpp
int max_batch_size = vkdev->info.compute_queue_count;

ncnn::Mat inputs[1000];
ncnn::Mat outputs[1000];

#pragma omp parallel for num_threads(max_batch_size)
for (int i = 0; i < 1000; i++) {
    ncnn::Extractor ex = net1.create_extractor();
    ex.input("data", inputs[i]);
    ex.extract("prob", outputs[i]);
}

5. 控制优化策略

禁用低精度优化,使用 float32 精度:

cpp
ncnn::Net net;
net.opt.use_fp16_packed = false;
net.opt.use_fp16_storage = false;
net.opt.use_fp16_arithmetic = false;
net.opt.use_int8_storage = false;
net.opt.use_int8_arithmetic = false;

6. 调试模型

目前 Vulkan 不能直接进行调试,需要修改 NCNN 的源码 src/gpu.cpp 中的宏指令:

cpp
#define ENABLE_VALIDATION_LAYER 1

修改为 1 可以获得调试输出。

参考

[1] https://github.com/Tencent/ncnn/wiki/vulkan-notes