【opengl学习】opengl的compute shader

目的

opengl虽然老,但是算上opengl es,应该是应用最广泛的显卡api。用compute shader做计算,可以一定程度上摆脱N卡的限制,也摆脱windows和linux,mac等平台的限制。
计算着色器应该没有完全榨干硬件的性能,但是也取得了可观的并行性。

compute shader的并发模型

在这里插入图片描述compute shader把并发任务拆成了一个三维的工作组,即一个并发任务可以有三个维度,我理解是为了方便索引。然后每个工作组下可以定义多个工作项,也有三个维度。工作组和工作项的区别在于,一个工作组的不同工作项可以共享变量,用share关键字即可,他们之间可以相互协同,类似于线程,可以完成比较复杂的工作。而工作组之间类似于进程,不方便共享,更独立。
一个shader只实现一个工作项的逻辑,工作项数目由shader指定,似乎不能动态设置,而工作项数目由api指定,可以动态设置。
在这里插入图片描述
在这里插入图片描述

计算着色器中的内置变量来进行索引:
假设一个任务有 (10,5,3)个工作组,每个工作组有(2,3,4)个工作项

  • gl_WorkGroupSize:全局工作组数量。三维数组(10,5,3)
  • gl_NumGroupSize: 每个工作组的工作项数,三维数组,即(2,3,4)
  • gl_WorkGroupID:当前工作组的全局ID。三维数组 ,范围是([0-9],[0-4],[0-2])
  • gl_LocalInvocationID:当前工作项的局部ID。三维数组,范围是([0-1],[0-2],[0-3])
  • gl_GlobalInvocationID:当前工作项的全局ID。三维数组,相当于gl_WorkGroupID和gl_LocalInvocationID拉平的id:
    • 比如第(5,2,1)工作组的第(1,2,1)个工作项的gl_GlobalInvocationID = (52+1, 23+2, 1*4+1) = (11,8,5)
  • gl_LocalInvocationIndex:当前工作项的线性索引。一个数,相当于拉成一维来进行唯一索引,即上一项进一步拉平

性能测试

比较cpu单核性能和集成显卡,我的pc参数如下:
windows11 专业版
cpu 13th Gen Intel® Core™ i5-1340P 1.90 GHz
显卡 Intel® Iris® Xe Graphics
在这里插入图片描述
测试 10002000的矩阵和20001000的矩阵相乘,这个显卡很垃圾
代码

#include <iostream>
#include <string>
#include <vector>
#include <random>
#include <chrono>
#include "glad/glad.h"
#include "GLFW/glfw3.h"
#include "img_util.h"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

std::string loadShaderSource(const std::string& shaderPath) {
	FILE* file = fopen(shaderPath.c_str(), "r");
	std::vector<char> res;
	if (file == NULL) {
		std::cout << "open shader file error:" << shaderPath << std::endl;
		return "";
	}
	// 计算大小
	fseek(file, 0, SEEK_END);
	long fileSize = ftell(file);
	fseek(file, 0, SEEK_SET);

	if (fileSize > 0) {
		res.resize(fileSize);
		long readSize = fread(res.data(), sizeof(char), fileSize, file);
		if (readSize > fileSize) {
			std::cout << "read shader file error:" << shaderPath
				<< "fileSize: " << fileSize << ",readSize: " << readSize
				<< " ,content: " << std::string(res.begin(), res.end()) << std::endl;
			return "";
		}
	}

	return std::string(res.begin(), res.end());

}

void randomFloatVector(std::vector<float>& vec, float min_value = -1.0f, float max_value= 1.0f) {
	size_t size = vec.size();
	// 创建一个随机数生成器
	std::random_device rd;
	std::mt19937 gen(rd());
	std::uniform_real_distribution<float> dis(min_value, max_value);

	// 创建一个 vector 并随机初始化
	for (size_t i = 0; i < size; ++i) {
		vec[i] = dis(gen);
	}
}

GLFWwindow* initWindow() {
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
	if (window == NULL)
	{
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return NULL;
	}
	glfwMakeContextCurrent(window);


	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		std::cout << "Failed to initialize GLAD" << std::endl;
		return NULL;
	}

	glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);

	return window;
}

int main(int argc, char** argv) {
	GLFWwindow* window = initWindow();
	if (!window) {
		std::cout << "init window failed" << std::endl;
		return -1;
	}

	int m = 1000;
	int n = 2000;
	std::string compteShaderPath = "D:\\projects\\cmake_proj\\shaders\\compute_shaders\\matmul.comp";

	std::cout << "init data begin" << std::endl;
	std::vector<float> xData(m * n); 
	randomFloatVector(xData);
	std::vector<float> wData(m * n);
	randomFloatVector(wData);
	std::vector<float> outDataGpu(m * m);
	std::vector<float> outDataCpu(m * m);

	GLuint xBuffer, wBuffer, outBuffer;

	std::string computeShaderSource = loadShaderSource(compteShaderPath);

	// 创建缓冲区
	glGenBuffers(1, &xBuffer);
	glGenBuffers(1, &wBuffer);
	glGenBuffers(1, &outBuffer);

	// 绑定并初始化 x 缓冲区
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, xBuffer);
	glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(float) * m * n, xData.data(), GL_STATIC_DRAW);

	// 绑定并初始化 w 缓冲区
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, wBuffer);
	glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(float) *  m * n, wData.data(), GL_STATIC_DRAW);

	// 绑定并初始化 out 缓冲区
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, outBuffer);
	glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(float) * m * m, nullptr, GL_STATIC_DRAW);

	// 创建shader
	std::cout << "load shader begin" << std::endl;
	GLuint computeShaderProgram = glCreateProgram();
	GLuint computeShader = glCreateShader(GL_COMPUTE_SHADER);
	const char* source = computeShaderSource.c_str();
	glShaderSource(computeShader, 1, &source, nullptr);
	glCompileShader(computeShader);

	// 检查编译错误
	GLint success;
	glGetShaderiv(computeShader, GL_COMPILE_STATUS, &success);
	if (!success) {
		char infoLog[512];
		glGetShaderInfoLog(computeShader, 512, nullptr, infoLog);
		std::cerr << "Compute shader compilation failed: " << infoLog << std::endl;
	}

	// 链接着色器程序
	glAttachShader(computeShaderProgram, computeShader);
	glLinkProgram(computeShaderProgram);

	// 检查链接错误
	glGetProgramiv(computeShaderProgram, GL_LINK_STATUS, &success);
	if (!success) {
		char infoLog[512];
		glGetProgramInfoLog(computeShaderProgram, 512, nullptr, infoLog);
		std::cerr << "Compute shader program linking failed: " << infoLog << std::endl;
	}

	//计时
	std::cout << "gpu compute begin" << std::endl;
	auto clk = std::chrono::high_resolution_clock();
	auto bg = clk.now();
	glUseProgram(computeShaderProgram);

	// 设置 n 
	glUniform1i(glGetUniformLocation(computeShaderProgram, "n"), n);
	glUniform1i(glGetUniformLocation(computeShaderProgram, "m"), m);
	
	glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, xBuffer);
	glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, wBuffer);
	glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, outBuffer);

	// 设置工作组大小
	glDispatchCompute(m, m, 1);

	// 确保所有计算完成
	glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);

	// 读取数据
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, outBuffer);
	glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, sizeof(float) * m * m, outDataGpu.data());

	auto ed = clk.now();
	auto gpuTime = std::chrono::duration_cast<std::chrono::microseconds>(ed - bg).count() / 1000.0;
	std::cout << "gpu time:" << gpuTime << "ms" << std::endl;

	bg = clk.now();
	for (int i = 0;i < m; ++i) {
		for (int j = 0;j < m; ++j) {
			float val = 0.0;
			for (int k = 0;k < n;++k) {
				val += xData[i * n + k] * wData[j + k * m];
			}
			outDataCpu[i * m + j] = val;
		}
	}
	ed = clk.now();
	auto cpuTime = std::chrono::duration_cast<std::chrono::microseconds>(ed - bg).count() / 1000.0;

	std::cout << "cpu time:" << cpuTime << "ms" << std::endl;

	float diff = 0.0;
	for (int i = 0;i < m*m; ++i) {
		diff += fabs(outDataGpu[i] - outDataCpu[i]);
	}

	std::cout << "diff: " << diff << ", avg:" << diff / (m * m) <<", cpu / gpu: " << cpuTime / gpuTime << std::endl;
	// 释放资源
	glDeleteBuffers(1, &xBuffer);
	glDeleteBuffers(1, &wBuffer);
	glDeleteBuffers(1, &outBuffer);
	glDeleteShader(computeShader);
	glDeleteProgram(computeShaderProgram);

	return 0;
}

shader

#version 460 core
uniform int n;
uniform int m;

layout(local_size_x = 1, local_size_y = 1) in;
layout(binding = 0) readonly buffer Input0 {
    float data[];
} x;
layout(binding = 1) readonly buffer Input1 {
    float data[];
} w;

layout(binding = 2) writeonly buffer Output0 {
    float data[];
} xout;

void main() {
    int i = int(gl_GlobalInvocationID.x); // x第i 行
    int j = int(gl_GlobalInvocationID.y); // w第j列
    float val = 0.0;
    for (int k=0; k<n; ++k) {
        val += x.data[i*n + k] * w.data[j + k *m ]; 
    }
    xout.data[i*m + j] = val;
}

采用了一个工作组数量都设置成1,gl_GlobalInvocationID等同于gl_WorkGroupID。运行结果
在这里插入图片描述
相比于单线程,可以获得大概8-10倍的速度提升

参考资料

https://github.com/SingingRivulet/transformer.gl.git
https://github.com/cgoxopx/llama2.gl
https://zhuanlan.zhihu.com/p/673144065
https://blog.csdn.net/qq_26328385/article/details/105526000

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CUDA - 如何让线程和内存对应

前提&#xff1a; 本文的目的就是设置的程序中&#xff0c;每个线程可以负责一个单独的计算任务。帮助学习和理解线程是如何组织的。 本文处理一个二维数据的加法。 数据在内存中的存储 以线性、行为主的方式存储。 例如&#xff0c;一个16*8的一维数组&#xff0c;在内存…

站在用户视角审视:以太彩光与PON之争

作者:科技作家-郑凯 园区,是企业数字化转型的“中心战场”。 云计算、大数据、人工智能等数智化技术在园区里“战火交织”;高清视频、协同办公,智慧安防等大量创新应用产生的海量数据在园区内“纵横驰骋”;加上大量的IOT和智能化设备涌入“战场”,让园区网络面对着难以抵御的…

查看PyTorch的GPU使用情况的工具

文章目录 torch.cuda APIPyTorch SnapshotPyTorch ProfilerNVIDIA Nsight Systemstorchinfo torch.cuda API torch.cuda.memory_stats&#xff1a;返回给定设备的 CUDA 内存分配器统计信息字典。该函数的返回值是一个统计字典&#xff0c;每个字典都是一个非负整数。torch.cud…

antDesign Form.List下的Form.Item如何通过setFieldsValue设置值

翻了一下antDesign官网只看见了Form可以使用setFieldsValue设置值&#xff0c;却没找到Form.List使用setFieldsValue设置值。 于是研究了一下&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 我的解决方案是&#xff1a; 先设置为空数组, 再设置成…

利用编程思维做题之反转链表

牛客网题目 1. 理解问题 给到我们的是一个单链表的头节点 pHead&#xff0c;要求反转后&#xff0c;返回新链表的头节点。 首先在心里设想能够快速理解的例子&#xff0c;如给你123序列&#xff0c;要你反转此序列如何回答&#xff1f;将最后一个数字3作为头&#xff0c;然后修…

学习threejs,THREE.MeshBasicMaterial网格材质、THREE.MeshLambertMaterial漫反射材质

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.MeshBasicMaterial网…

MATLAB代码解析:利用DCGAN实现图像数据的生成

摘要 经典代码&#xff1a;利用DCGAN生成花朵 MATLAB官方其实给出了DCGAN生成花朵的示范代码&#xff0c;原文地址&#xff1a;训练生成对抗网络 (GAN) - MATLAB & Simulink - MathWorks 中国 先看看训练效果 训练1周期 训练11周期 训练56个周期 脚本文件 为了能让各位…

centos7 Oracle 11g rac 静默安装(NFS配置共享存储)

1.环境信息准备 注意&#xff1a; 在配置网络时&#xff0c;Oracle RAC的每个节点必须具有至少两个以上的网卡&#xff0c;一张网卡对外提供网络服务&#xff0c;另一张网卡用于各个节点间的通信和心跳检测等。在配置RAC集群的网卡时&#xff0c;如果节点1的公共接口是eth0&…

下一代安全:融合网络和物理策略以实现最佳保护

在当今快速发展的技术环境中&#xff0c;网络和物理安全融合变得比以往任何时候都更加重要。随着物联网 (IoT) 和工业物联网 (IIoT) 的兴起&#xff0c;组织在保护数字和物理资产方面面临着独特的挑战。 本文探讨了安全融合的概念、说明其重要性的实际事件以及整合网络和物理安…

本地装了个pytorch cuda

安装命令选择 pip install torch1.13.1cu116 torchvision0.14.1cu116 torchaudio0.13.1 --extra-index-url https://download.pytorch.org/whl/cu116 torch版本查看 python import torch print(torch.__version__) 查看pytorch能否使用cuda import torch# 检查CUDA是否可用…

鸿蒙NEXT开发-动画(基于最新api12稳定版)

注意&#xff1a;博主有个鸿蒙专栏&#xff0c;里面从上到下有关于鸿蒙next的教学文档&#xff0c;大家感兴趣可以学习下 如果大家觉得博主文章写的好的话&#xff0c;可以点下关注&#xff0c;博主会一直更新鸿蒙next相关知识 专栏地址: https://blog.csdn.net/qq_56760790/…

241014-绿联UGOSPro-通过虚拟机访问主机的用户目录及文件夹

如图所示&#xff0c;两种方式&#xff1b; 方式1: 通过Files中的Other Locations 添加主机ip&#xff0c;随后输入主机的用户名及密码即可系统及文件加载可能需要一段时间&#xff0c;有点卡&#xff0c;加载完应该就可以点击访问了 方式2: 通过命令行直接ssh/sftp userna…

【C++网络编程】(一)Linux平台下TCP客户/服务端程序

文章目录 Linux平台下TCP客户/服务端程序服务端客户端相关头文件介绍 Linux平台下TCP客户/服务端程序 图片来源&#xff1a;https://subingwen.cn/linux/socket/ 下面实现一个Linux平台下TCP客户/服务端程序&#xff1a;客户端向服务器发送&#xff1a;“你好&#xff0c;服务…

网络资源模板--Android Studio 实现简易新闻App

目录 一、项目演示 二、项目测试环境 三、项目详情 四、完整的项目源码 一、项目演示 网络资源模板--基于Android studio 实现的简易新闻App 二、项目测试环境 三、项目详情 登录页 用户输入&#xff1a; 提供账号和密码输入框&#xff0c;用户可以输入登录信息。支持“记…

[ComfyUI]Flux:国漫经典!斗破苍穹古熏儿之绮梦流光模型来袭

在数字艺术和创意领域&#xff0c;FLUX以其独特的虚实结合技术&#xff0c;已经成为艺术家和设计师们手中的利器。今天&#xff0c;我们激动地宣布&#xff0c;FLUX推出了一款全新的ComfyUI版本——Flux&#xff0c;它将国漫经典《斗破苍穹》中的古熏儿之绮梦流光模型完美融合&…

第十四章 RabbitMQ延迟消息之延迟队列

目录 一、引言 二、死信队列 三、核心代码实现 四、运行效果 五、总结 一、引言 什么是延迟消息&#xff1f; 发送者发送消息时指定一个时间&#xff0c;消费者不会立刻收到消息&#xff0c;而是在指定时间后收到消息。 什么是延迟任务&#xff1f; 设置在一定时间之后才…

Qt入门教程:创建我的第一个小程序

本章教程&#xff0c;主要介绍如何编写一个简单的QT小程序。主要是介绍创建项目的过程。 一、打开QT软件编辑器 这里使用的是QT5.14.2版本的&#xff0c;安装教程参考以往教程&#xff1a;https://blog.csdn.net/qq_19309473/article/details/142907096 二、创建项目 到这里&am…

使用Docker部署nextjs应用

最近使用nextjs网站开发&#xff0c;希望使用docker进行生产环境的部署&#xff0c;减少环境的依赖可重复部署操作。我采用的是Dockerfile编写应用镜像方式 docker-compose实现容器部署的功能。 Docker Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器…

【大模型问答测试】大模型问答测试脚本实现(第一版)

背景 公司已经做了一段时间的大模型&#xff0c;每次测试或者回归的时候都需要针对问答进行测试回归&#xff0c;耗费大量的时间与精力&#xff0c;因此结合产品特点&#xff0c;开发自动化脚本替代人工的操作&#xff0c;提升测试回归效率 设计 使用pythonrequestExcel进行…

Android笔记(二十四)基于Compose组件的MVVM模式和MVI模式的实现

仔细研究了一下MVI(Model-View-Intent)模式&#xff0c;发现它和MVVM模式非常的相识。在采用Android JetPack Compose组件下&#xff0c;MVI模式的实现和MVVM模式的实现非常的类似&#xff0c;都需要借助ViewModel实现业务逻辑和视图数据和状态的传递。在这篇文章中&#xff0c…