NCNN GPU初始化加速——cache实现

news2024/10/7 16:23:03

概要

 NCNN的CPU初始化速度很快,但是当使用GPU进行推理时,初始化往往要花费几秒甚至更长时间。其他框架例如MNN有载入cache的方式来进行加速,NCNN目前没有相关接口来实现加速,那么NCNN是否也可以加载cache来实现加速呢?

整体流程

通过测速以及查看NCNN的源码可以发现,在gpu.cpp源文件下的VulkanDevice::create_pipeline函数内的vkCreateComputePipelines占了相当长时间,而vkCreateComputePipelines是vulkan的一个函数,该函数可以通过载入pipelineCache来实现加速。本文所做的工作就是通过生成读取这个pipelineCache来进行加速的。

具体实现

NCNN的GPU初始化加速分为写和读两部分。

写:

因为create_pipeline这个函数会被执行多次,例如我这边两个模型需要执行171次,所以保存文件的时候需要一个计数器来对文件分别进行命名,我这边创建了一个GlobalCounter.cpp,这个源文件的内容很简单,新建一个globalCounter 变量。

int globalCounter = 0;

gpu.cpp包含这个cpp文件,#include "GlobalCounter.cpp"。

在每次保存的时候globalCounter++进行自增来区分不同的cache文件。

修改原来的vkCreateComputePipelines,改成如下:

VkPipelineCache pipelineCache;
VkPipelineCacheCreateInfo cacheCreateInfo{};
cacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
// 创建VkPipelineCache对象
VkResult result = vkCreatePipelineCache(d->device, &cacheCreateInfo, nullptr, &pipelineCache);

VkResult ret = vkCreateComputePipelines(d->device, pipelineCache, 1, &computePipelineCreateInfo, 0, pipeline);

// 将pipelineCache的值保存到本地文件
globalCounter++;
char filename[50];
sprintf(filename, "/sdcard/dcim/tmp/%d.bin", globalCounter);
FILE* fp = nullptr;
VkDeviceSize size;
VkResult res = vkGetPipelineCacheData(d->device, pipelineCache, &size, nullptr);
if (res != VK_SUCCESS) {
   NCNN_LOGE("Error getting size of pipeline cache %d\n", res);
}
void* data = malloc(size);
res = vkGetPipelineCacheData(d->device, pipelineCache, &size, data);
if (res != VK_SUCCESS) {
   NCNN_LOGE("Error getting data of pipeline cache %d\n", res);
}
fp = fopen(filename, "wb");
if (!fp) {
   NCNN_LOGE("Failed to open file for writing.\n");
}
fwrite(data, size, 1, fp);
fclose(fp);
free(data);


改完后重新编译并生成ncnn库,再在设备上运行一次。cache文件就被保存到了指定的地方。

可以按照写的方式,通过一个计数器来分别一次读取二进制文件。为了更快的进行初始化,我的想法是合并这些cache文件,通过创建一个数组,把cache文件的数据全部放到数组里去,在初始化的时候直接读数组的内容,省去了读的这个操作,实测下来相比依次读取二进制文件两个模型总共快了1s。

因此我的实现还多了一步,合并:

    if (globalCounter == 0)
    {
        VkPipelineCache pipelineCache[171];
        VkPipelineCache MergeCache;
        VkPipelineCacheCreateInfo MergecacheCreateInfo{};
        MergecacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
        VkResult result = vkCreatePipelineCache(d->device, &MergecacheCreateInfo, nullptr, &MergeCache);
        for (int i = 0; i < 171; i++)
        {
            char filename[50];
            sprintf(filename, "/sdcard/dcim/tmp/%d.bin", (i + 1));
            std::ifstream file(filename, std::ios::binary);
            file.seekg(0, std::ios::end);
            size_t fileSize = static_cast<size_t>(file.tellg());
            file.seekg(0, std::ios::beg);
            std::vector<char> cacheData(fileSize);
            file.seekg(0);
            file.read(cacheData.data(), fileSize);
            VkPipelineCacheCreateInfo cacheCreateInfo{};
            cacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
            cacheCreateInfo.initialDataSize = fileSize;
            cacheCreateInfo.pInitialData = cacheData.data();
            VkResult result = vkCreatePipelineCache(d->device, &cacheCreateInfo, nullptr, &pipelineCache[i]);
        }
        vkMergePipelineCaches(d->device, MergeCache, 171, pipelineCache);
        char filename[50];
        sprintf(filename, "/sdcard/dcim/tmp/MergeCache.bin");
        FILE* fp = nullptr;
        VkDeviceSize size;
        VkResult res = vkGetPipelineCacheData(d->device, MergeCache, &size, nullptr);
        if (res != VK_SUCCESS) {
            NCNN_LOGE("Error getting size of pipeline cache %d\n", res);
        }
        void* data = malloc(size);
        res = vkGetPipelineCacheData(d->device, MergeCache, &size, data);
        if (res != VK_SUCCESS) {
            NCNN_LOGE("Error getting data of pipeline cache %d\n", res);
        }
        fp = fopen(filename, "wb");
        if (!fp) {
            NCNN_LOGE("Failed to open file for writing.\n");
        }
        fwrite(data, size, 1, fp);
        fclose(fp);
        free(data);
    }

通过第一步写可以得知总共创建了多少cache文件,我这边一共是171个cache文件。随后利用vulkan的vkMergePipelineCaches来合并cache文件,最后将MergeCache保存到本地。因为我还是放在create_pipeline函数下,这个函数是要执行100多次的,而合并文件只需要执行一次,所以我这边通过globalCounter == 0来让它只执行一次合并操作。

合并完后,因为我们要把它放到数组里去,而MergeCache.bin是一个二进制文件,里面的内容如何复制呢?这里推荐使用HxD,它可以读取二进制文件并将其导出为c文件,它会自动把数据全部存到一个数组里去。

读:

重新生成新的ncnn文件,因为原来写的内容在读的时候并不需要。修改MergeCache.c,内容如下:

#include <vulkan/vulkan.h>

VkPipelineCache GlobalPipelineCache = 0;

unsigned char CacheData[759974] = {xxxxxxxxxxxxxxxx}

void CreateGlobalPipelineCache(VkDevice *device)
{
	VkPipelineCacheCreateInfo cacheCreateInfo{};
	cacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
	cacheCreateInfo.initialDataSize = sizeof(CacheData);
	cacheCreateInfo.pInitialData = CacheData;
	VkResult result = vkCreatePipelineCache(*device, &cacheCreateInfo, nullptr, &GlobalPipelineCache);
}

创建了一个GlobalPipelineCache 变量,并定义了创建GlobalPipelineCache的函数。CacheData是HxD转换好的数组,因为数据过多,这边用xxxx表示其内容。并将MergeCache.c重命名为PipelineCacheData.cpp。

然后在gpu.cpp下包含这个源文件,#include "PipelineCacheData.cpp"。

CreateGlobalPipelineCache我把它放在了

VulkanDevice::VulkanDevice(int device_index)
    : info(get_gpu_info(device_index)), d(new VulkanDevicePrivate(this)) 

函数最后,其实只要让CreateGlobalPipelineCache放在create_pipeline实现的前面任一函数内且保证这个函数只执行一次就可以了,我这边选择的是VulkanDevice函数下。

最后修改vkCreateComputePipelines,将原来的

VkResult ret = vkCreateComputePipelines(d->device, 0, 1, &computePipelineCreateInfo, 0, pipeline);

改为

VkResult ret = vkCreateComputePipelines(d->device, GlobalPipelineCache, 1, &computePipelineCreateInfo, 0, pipeline);

重新编译ncnn库文件,至此NCNN GPU初始化加速——cache实现就全部完成了。

初始化速度对比:

方法初始化速度
NCNN8s
NCNN-cache4.6s
MNN-cache3.1s

这里的初始化速度包含了项目的整个初始化时间,并不单单只是模型的载入。具体速度的话对于NCNN-cache 库,NCNN初始化时间:创建shader 1.5s,创建pipeline 0.3s ,上传模型2s。剩下的时间花在其他初始化上。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1437905.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

程序员的数字化工作台:理解不关机背后的逻辑与需求

目录 程序员为什么不喜欢关电脑&#xff1f; 电脑对程序员的重要性&#xff1a; 工作流程与需求&#xff1a; 数据安全与备份&#xff1a; 即时性与响应&#xff1a; 个人习惯等方面&#xff1a; 程序员为什么不喜欢关电脑&#xff1f; 电脑对程序员的重要性&#xff1a;…

龙测科技荣获2023年度技术生态构建奖

本月&#xff0c;由极客传媒举办的“有被Q到”2024 InfoQ 极客传媒合作伙伴年会顺利举办&#xff0c;龙测科技喜获2023年度技术生态构建奖。 InfoQ是首批将Node.js、HTML5、Docker等技术全面引入中国的技术媒体之一&#xff0c;秉承“扎根社区、服务社区、引领社区”的理念&…

Redis(十三)缓存双写一致性策略

文章目录 概述示例 缓存双写一致性缓存按照操作来分&#xff0c;细分2种读写缓存&#xff1a;同步直写策略读写缓存&#xff1a;异步缓写策略双检加锁策略 数据库和缓存一致性更新策略先更新数据库&#xff0c;再更新缓存先更新缓存&#xff0c;再更新数据库先删除缓存&#xf…

解决国内无法访问OpenAI API的三种方式

前言 在全球数字化的浪潮中&#xff0c;人工智能API成为了推动创新的关键工具。然而&#xff0c;由于网络限制&#xff0c;不是所有用户都能直接访问这些资源。国内就不能直接访问OpenAI官网&#xff0c;也就不能直接访问OpenAI API&#xff0c;这时候需要去寻找OpenAI的代理方…

DevExpress WinForms中文教程 - 如何创建可访问的WinForms应用?(二)

为用户创建易访问的Windows Forms应用程序不仅是最佳实践的体现&#xff0c;还是对包容性和以用户为中心的设计承诺。在应用程序开发生命周期的早期考虑与可访问性相关的需求可以节省长期运行的时间(因为它将决定设计决策和代码实现)。 一个可访问的WinForms应用程序提供了各种…

Python循环语句——for循环的基础语法

一、引言 在Python编程的世界中&#xff0c;for循环无疑是一个强大的工具。它为我们提供了一种简洁、高效的方式来重复执行某段代码&#xff0c;从而实现各种复杂的功能。无论你是初学者还是资深开发者&#xff0c;掌握for循环的用法都是必不可少的。在本文中&#xff0c;我们…

EasyRecovery2024永久免费版电脑数据恢复软件下载

EasyRecovery数据恢复软件是一款非常好用且功能全面的工具&#xff0c;它能帮助用户恢复各种丢失或误删除的数据。以下是关于EasyRecovery的详细功能介绍以及下载步骤&#xff1a; EasyRecovery-mac最新版本下载:https://wm.makeding.com/iclk/?zoneid50201 EasyRecovery-win…

2 月 7 日算法练习- 数据结构-树状数组

树状数组 lowbit 在学习树状数组之前&#xff0c;我们需要了解lowbit操作&#xff0c;这是一种位运算操作&#xff0c;用于计算出数字的二进制表达中的最低位的1以及后面所有的0。 写法很简单&#xff1a; int lowbit&#xff08;int x&#xff09;&#xff5b;return x &am…

基于SpringBoot+Vue的实验室管理系统

末尾获取源码作者介绍&#xff1a;大家好&#xff0c;我是墨韵&#xff0c;本人4年开发经验&#xff0c;专注定制项目开发 更多项目&#xff1a;CSDN主页YAML墨韵 学如逆水行舟&#xff0c;不进则退。学习如赶路&#xff0c;不能慢一步。 目录 一、项目简介 二、开发技术与环…

vHierarchy

与其他层次结构资产不同,vHierarchy是: -极简:没有噱头或视觉杂乱 - 可定制:任何功能都可以禁用 - 优化:无编辑器延迟 - 安全:没有隐藏的游戏对象,卸载后不会搞乱你的项目 特点: 组件迷你地图 - 见右侧列出的组件 - 按住Alt键并单击组件图标,打开组件编辑器弹出窗口 自…

RCS系统之:机器人状态

在设计RCS系统平台时&#xff0c;机器人总共设计状态有&#xff1a; 离线模式&#xff1b; 如图&#xff0c;18号机器人呈灰黑色&#xff0c;表示机器人没有上电状态 工作模式&#xff1b; 如图&#xff0c;10号机器人成绿色&#xff0c;表示机器人处于工作模式&#xff0c;等…

###C语言程序设计-----C语言学习(10)#函数再探

前言&#xff1a;感谢您的关注哦&#xff0c;我会持续更新编程相关知识&#xff0c;愿您在这里有所收获。如果有任何问题&#xff0c;欢迎沟通交流&#xff01;期待与您在学习编程的道路上共同进步。 目录 一. 基础知识的学习 1.不返回结果函数 2.局部变量 3.全局变量 4.…

linux中的gdb调试

gdb是在程序运行的结果与预期不符合时&#xff0c;可以使用gdb进行调试 注意&#xff1a;使用gdb调试时要在编译上加-g参数 gcc -g -c hello.c 启动gdb调试&#xff1a; gdb file 对gdb进行调试 设置运行参数&#xff1a; set args 可指定运行参数 show args 可以查…

【DC-9靶场渗透】

文章目录 前言 一、确定靶机地址 二、信息收集 三、寻找漏洞 四、进一步漏洞挖掘 五、关键文件 六、ssh爆破 七、提权 总结 前言 马上过年了&#xff0c;年前再做一下DC靶场最后一个靶机。 一、确定靶机地址 1、可使用arp-scan命令 靶机地址为&#xff1a;172.16.10…

编译原理与技术(三)——语法分析(六)自底向上-SLR分析

上一节介绍了LR分析&#xff0c;LR分析包含许多方法&#xff0c;本节介绍的简单的LR方法&#xff08;SLR&#xff09;就是其中之一。 一、活前缀 二、LR分析的特点 三、 简单的LR方法&#xff08;SLR&#xff09; &#xff08;一&#xff09;LR(0)项目 &#xff08;二&#x…

ES6扩展运算符——三个点(...)用法详解

目录 1 含义 2 替代数组的 apply 方法 3 扩展运算符的应用 &#xff08; 1 &#xff09;合并数组 &#xff08; 2 &#xff09;与解构赋值结合 &#xff08; 3 &#xff09;函数的返回值 &#xff08; 4 &#xff09;字符串 &#xff08; 5 &#xff09;实现了 Iter…

3. 私服方面

目录 3.1 场景 3.2 介绍 3.3 资源上传与下载 3.3.1 步骤分析​编辑 3.3.2 具体操作 maven1&#xff1a;分模块设计开发 maven2&#xff1a;继承与聚合 3.私服 前面我们在讲解多模块开发的时候&#xff0c;我们讲到我们所拆分的模块是可以在同一个公司各个项目组之间进行…

关于PLC数据采集上报,系统平台对接、设备数据转发

设备数据采集上报与系统平台对接 相关案例 PLC与SQLServer&#xff0c;MySQL&#xff0c;PostgreSQL&#xff0c;Oracle数据库双向通讯&#xff1b;HTTP协议GET/POST/PUT请求上报&#xff0c;解析返回数据&#xff1b;MQTT协议JSON/XML文件格式发布/订阅&#xff1b;无需…

MyBatis:轻量级Java持久层框架初探

引言 在Java企业级应用开发领域&#xff0c;ORM框架无疑是构建高性能数据访问层的关键工具之一。MyBatis作为一款轻量级、易于学习且高度可定制化的持久层框架&#xff0c;以其简洁的设计理念、卓越的灵活性和高效的SQL处理能力&#xff0c;赢得了广大开发者的青睐。本文将系统…

肯尼斯·里科《C和指针》第12章 使用结构和指针(2)双链表

12.3 双链表 单链表的替代方案就是双链表。在一个双链表中&#xff0c;每个节点都包含两个指针——指向前一个节点的指针和指向后一个节点的指针。这可以使我们以任何方向遍历双链表&#xff0c;甚至可以随意在双链表中访问。下面的图展示了一个双链表。 下面是节点类型的声明&…