第 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 的支持性:
支持性 | Windows | Linux | Android | Mac | iOS |
---|---|---|---|---|---|
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
可以获得调试输出。