9. CUDA shared memory使用------GPU的革命

CUDA加速——共享内存介绍及其应用 CUDA存储器的分类与介绍 阅读详情

9. CUDA shared memory使用------GPU的革命

序言:明年就毕业了,下半年就要为以后的生活做打算。这半年,或许就是一个抉择的时候,又是到了一个要做选择的时候。或许是自己的危机意识比较强,一直都觉得自己做得不够好,还需要积累和学习。或许是知足常乐吧,从小山沟,能到香港,一步一步,自己都比较满足,只是心中一直抱着一个理想,坚持做一件事情,坚持想做点事情,踏踏实实,曾经失败过,曾经迷茫过,才学会了坚持,学会了坚毅,才体会了淡定和从容。人生路上,一路走,一路看,一路学,抱着感恩的心,帮助别人,就是帮助自己,未来的路才会更宽……

正文:书接上文《8. CUDA 内存使用 global 二------GPU的革命》 讲了global内存访问的时候,需要对齐的问题,只有在对齐的情况下才能保证global内存的高效访问。这一章节准备写一下shared memory的访问的问题,首先是讲一下shared的memory的两种使用方法,然后讲解一下shared memory的bank conflict的问题,这个是shared memory访问能否高效的问题所在;

Shared memory的常规使用:

1. 使用固定大小的数组:

/************************************************************************/

/* Example */

/************************************************************************/

__global__ void shared_memory_1(float* result, int num, float* table_1)

{

__shared__ float sh_data[THREAD_SIZE];

int idx = threadIdx.x;

float ret = 0.0f;

sh_data[idx] = table_1[idx];

for (int i = 0; i < num; i++)

{

ret += sh_data[idx %BANK_CONFLICT];

}

result[idx] = ret;

}

这里的sh_data就是固定大小的数组;

2. 使用动态分配的数组:

extern __shared__ char array[];

__global__ void shared_memory_1(float* result, int num, float* table_1, int shared_size)

{

float* sh_data = (float*)array; // 这里就让sh_data指向了shared memory的第一个地址,就可以动态分配空间

float* sh_data2 = (float*)&sh_data[shared_size]; // 这里的shared_size的大小为sh_data的大小;

int idx = threadIdx.x;

float ret = 0.0f;

sh_data[idx] = table_1[idx];

for (int i = 0; i < num; i++)

{

ret += sh_data[idx %BANK_CONFLICT];

}

result[idx] = ret;

}

这里是动态分配的空间,extern __shared__ char array[];指定了shared的第一个变量的地址,这里其实是指向shared memory空间地址;后面的动态分配float* sh_data = (float*)array;让sh_data指向array其实就是指向shared memory上的第一个地址;

后面的float* sh_data2 = (float*)&sh_data[shared_size];这里的sh_data2是指向的第一个sh_data的shared_size的地址,就是sh_data就是有了shared_size的动态分配的空间;

入下图:

clip_image002[8]

3. 下面是讲解bank conflict

我们知道有每一个half-warp是16个thread,然后shared memory有16个bank,怎么分配这16个thread,分别到各自的bank去取shared memory,如果大家都到同一个bank取款,就会排队,这就造成了bank conflict,上面的代码可以用来验证一下bank conflict对代码性能造成的影响:

/************************************************************************/

/* Example */

/************************************************************************/

__global__ void shared_memory_1(float* result, int num, float* table_1)

{

__shared__ float sh_data[THREAD_SIZE];

int idx = threadIdx.x;

float ret = 0.0f;

sh_data[idx] = table_1[idx];

for (int i = 0; i < num; i++)

{

ret += sh_data[idx %BANK_CONFLICT];

}

result[idx] = ret;

}

// 1,2,3,4,5,6,7.....16

#define BANK_CONFLICT 16

这里的BANK_CONFLICT 定义为从1到16的大小,可以自己修改,来看看bank conflict对性能的影响;当BANK_CONFLICT为2的时候,就会通用有8个thread同时访问同一个bank,因为idx%2的取值只有2个0和1,所以16个都会访问bank0和bank1,以此类推,就可以测试整个的性能;

下面为示意图:

clip_image004[9]

当然我们还可以利用16bank conflict,大家都访问同一个bank的同一个数据的时候,就可以形成一个broadcast,那样就会把数据同时广播给16个thread,这样就可以合理利用shared memory的broadcast的机会。

下面贴出代码,最好自己测试一下;

/********************************************************************

* shared_memory_test.cu

* This is a example of the CUDA program.

* Author: zhao.kaiyong(at)gmail.com

* http://blog.youkuaiyun.com/openhero

* http://www.comp.hkbu.edu.hk/~kyzhao/

*********************************************************************/

#include

#include

#include

#include

// 1,2,3,4,5,6,7.....16

#define BANK_CONFLICT 16

#define THREAD_SIZE 16

/************************************************************************/

/* static */

/************************************************************************/

__global__ void shared_memory_static(float* result, int num, float* table_1)

{

__shared__ float sh_data[THREAD_SIZE];

int idx = threadIdx.x;

float ret = 0.0f;

sh_data[idx] = table_1[idx];

for (int i = 0; i < num; i++)

{

ret += sh_data[idx%BANK_CONFLICT];

}

result[idx] = ret;

}

/************************************************************************/

/* dynamic */

/************************************************************************/

extern __shared__ char array[];

__global__ void shared_memory_dynamic(float* result, int num, float* table_1, int shared_size)

{

float* sh_data = (float*)array; // 这里就让sh_data指向了shared memory的第一个地址,就可以动态分配空间

float* sh_data2 = (float*)&sh_data[shared_size]; // 这里的shared_size的大小为sh_data的大小;

int idx = threadIdx.x;

float ret = 0.0f;

sh_data[idx] = table_1[idx];

for (int i = 0; i < num; i++)

{

ret += sh_data[idx%BANK_CONFLICT];

}

result[idx] = ret;

}

/************************************************************************/

/* Bank conflict */

/************************************************************************/

__global__ void shared_memory_bankconflict(float* result, int num, float* table_1)

{

__shared__ float sh_data[THREAD_SIZE];

int idx = threadIdx.x;

float ret = 0.0f;

sh_data[idx] = table_1[idx];

for (int i = 0; i < num; i++)

{

ret += sh_data[idx % BANK_CONFLICT];

}

result[idx] = ret;

}

/************************************************************************/

/* HelloCUDA */

/************************************************************************/

int main(int argc, char* argv[])

{

if ( cutCheckCmdLineFlag(argc, (const char**) argv, "device"))

{

cutilDeviceInit(argc, argv);

}else

{

int id = cutGetMaxGflopsDeviceId();

cudaSetDevice(id);

}

float *device_result = NULL;

float host_result[THREAD_SIZE] ={0};

CUDA_SAFE_CALL( cudaMalloc((void**) &device_result, sizeof(float) * THREAD_SIZE));

float *device_table_1 = NULL;

float host_table1[THREAD_SIZE] = {0};

for (int i = 0; i < THREAD_SIZE; i++ )

{

host_table1[i] = rand()%RAND_MAX;

}

CUDA_SAFE_CALL( cudaMalloc((void**) &device_table_1, sizeof(float) * THREAD_SIZE));

CUDA_SAFE_CALL( cudaMemcpy(device_table_1, host_table1, sizeof(float) * THREAD_SIZE, cudaMemcpyHostToDevice));

unsigned int timer = 0;

CUT_SAFE_CALL( cutCreateTimer( &timer));

CUT_SAFE_CALL( cutStartTimer( timer));

shared_memory_static<<<1, THREAD_SIZE>>>(device_result, 1000, device_table_1);

//shared_memory_dynamic<<<1, THREAD_SIZE>>>(device_result, 1000, device_table_1, 16);

//shared_memory_bankconflict<<<1, THREAD_SIZE>>>(device_result, 1000, device_table_1);

CUT_CHECK_ERROR("Kernel execution failed/n");

CUDA_SAFE_CALL( cudaMemcpy(host_result, device_result, sizeof(float) * THREAD_SIZE, cudaMemcpyDeviceToHost));

CUT_SAFE_CALL( cutStopTimer( timer));

printf("Processing time: %f (ms)/n", cutGetTimerValue( timer));

CUT_SAFE_CALL( cutDeleteTimer( timer));

for (int i = 0; i < THREAD_SIZE; i++)

{

printf("%f ", host_result[i]);

}

CUDA_SAFE_CALL( cudaFree(device_result));

CUDA_SAFE_CALL( cudaFree(device_table_1));

cutilExit(argc, argv);

}

这里只是一个简单的demo,大家可以测试一下。下一章节会将一些shared memory的更多的特性,更深入的讲解shared memory的一些隐藏的性质;

再在接下来的章节会讲一些constant和texture的使用;

写的内容一直都是文字比较多,代码比较少,其实学习的过程更重要的思想,实践的代码,最好是自己写,唯一可以学习的是思想,学习更重要的也是思想的交流,知识的传播,最好的是思想的传播,代码,方法,都是只是一些工具而已。但是工具的熟练层度,就得靠自己下来多练习。

从晶体管到SIMT:CUDA底层原理与硬件级性能优化 GPU并行计算并非简单加速CPU代码,而是基于SIMT(单指令多线程)执行模型、分层内存体系与warp调度机制的全新范式。其核心原理植根于芯片物理设计:SM中32线程warp的零开销切换、shared memory的32-bank交错映射、global memory的64字节burst对齐要求,共同决定了kernel性能上限。理解这些硬件约束,才能突破‘内存墙’瓶颈、规避bank conflict与warp divergence,并在矩阵乘、图像处理、AI推理等典型场景中实现接近理论带宽的访存效率和高war 阅读详情

相关推荐

GPU显存层次结构:H100/HBM3/L2缓存/TMA硬件优化实战

GPU性能瓶颈往往不在计算单元,而在数据供给系统——即显存层次结构(Memory Hierarchy)。它涵盖寄存器、共享内存、L1/L2缓存、HBM高带宽显存及NVLink/CXL互连等多级组件,共同决定数据访问延迟与带宽效率。理解其物理布局与一致性策略,是突破AI训练吞吐瓶颈、降低推理延迟的关键前提。尤其在H100等新一代架构中,50MB L2缓存、HBM3显存与Tensor Memory Accelerator(TMA)硬件协同,显著改变传统CUDA访存范式。本文结合真实调试日志与nsys性能分析,解

weixin_34195546的博客 314

CUDA共享内存操作(__shared__关键字)

CUDA 对GPU的显存操作时,若线程频繁对某个数据进行读写操作,可以设置操作的数据常驻缓存,这样可以进一步提高代码的运行效率,并且同一个线程块内的所有线程共享该内存区域。然而当出现多个线程对同一个内存区域进行操作时,需要对线程进行同步操作,从而避免竞争的发生。cuda中使用__shared__关键字,这里使用__syncthreads()控制线程同步。 如核函数dot所示,代码生成一个数组并计算...

steppad 1万+

Conda 搭建 Ultralytics 环境:解决 PyTorch CUDA 版本错配与 GPU 不可用问题

Python 依赖管理是深度学习工程落地的核心瓶颈,尤其在 YOLO 等多栈耦合框架中,PyTorch、CUDA、cuDNN 和 OpenCV 的版本兼容性直接决定模型能否启动与推理。传统 pip 仅管理 Python 包,无法校验系统级依赖(如 NVIDIA 驱动版本、libcudnn ABI 兼容性),导致运行时报错频发;而 Conda 作为跨语言环境调度器,通过声明式约束与求解器验证,在安装阶段即拦截不兼容组合。其技术价值在于实现环境可重现性与部署确定性,广泛应用于工业质检、边缘推理等对稳定性要求严苛

culi4814的博客 509

告诉你cuda共享内存的使用

想必大家都知道,cuda里面每一个block上有一块高速缓冲区,这就是提供给block里面各个线程使用的shared memory,那怎么使用这一块内存呢? 首先,shared memory分为固定分配方式和动态分配方式,就是上图的Static Shared Memory和Dynamic Shared Memory 1,固定分配 直接__shared__ int seme[5] ;这就是在每一个block里面分配5个int(20B) __global__ void addKernel(int *c, c.

qq_37542753的博客 1924

【Python高阶编程技术】第32篇 内存格式革命:Apache Arrow、PyArrow、Plasma Shared Memory

本文深入解析Apache Arrow技术如何通过列式内存格式实现跨语言零拷贝数据共享。首先对比传统行式存储的瓶颈,展示Arrow在"大宽表"场景的优势。接着剖析Arrow内存布局、共享机制(Plasma/Flight)及Substrait统一计算计划。通过3个实战案例:1)零拷贝ETL流水线使处理速度提升6倍;2)Plasma共享内存实现进程间毫秒级数据传输;3)Substrait计划驱动多引擎执行。文章还总结了常见陷阱及最佳实践,如避免数据复制、版本一致性管理等。最终指出掌握Arrow

1563

GPU架构演进:从CUDA核心到Tensor Core,揭秘AI算力底层逻辑与优化实践

GPU(图形处理器)作为现代并行计算的核心硬件,其架构演进深刻影响着计算范式。其底层原理基于大规模并行处理,通过流式多处理器(SM)组织成千上万个计算核心协同工作。这一技术价值在于将原本专用于图形渲染的硬件,转变为支撑人工智能训练与推理、高性能科学计算的通用算力引擎。其核心应用场景已从传统的图形处理,扩展到深度学习模型训练、大数据分析和云计算等领域。本文聚焦于GPU架构如何通过引入Tensor Core等专用计算单元来加速矩阵运算,并结合CUDA编程模型中的线程束调度与内存层级优化,深入解析了在AI大模型时

weixin_30411819的博客 375

CUDA学习(九):共享内存

转自 CUDA学习笔记(6) 共享内存与全局内存    共享内存(Shared memory)是位于每个流处理器组(SM)中的高速内存空间,主要作用是存放一个线程块(Block)中所有线程都会频繁访问的数据。流处理器(SP)访问它的速度仅比寄存器(Register)慢,它的速度远比全局显存快。但是他也是相当宝贵的资源,一般只有几十KByte。 全局显存(Global memory)的大小比共享显存(Shared memory)大了几个数量级。当然,共享显存的访问速度也一定比全局显存快。再从硬件上看:  

hjxu2016的博客 3653

vLLM部署排坑指南:CUDA显存报错与环境兼容性实战

vLLM作为高性能大模型推理引擎,其核心能力依赖于PagedAttention、Continuous Batching等底层机制,而这些机制的稳定运行高度依赖CUDA驱动、cuBLAS、NCCL与Python运行时的精准匹配。理解GPU显存分配原理(如`--gpu-memory-utilization`实际控制KV Cache内存池)、掌握CUDA版本兼容性规则(如驱动525.60.13+对应CUDA 12.1),是解决`CUDA out of memory`和`no GPU found`等高频故障的前提。

weixin_34335458的博客 375

(CUDA 编程9).CUDA shared memory使用------GPU的革命

(CUDA 编程9).CUDA shared memory使用------GPU的革命 作者:赵开勇 来源:http://www.hpctech.com/2009/0818/207.html 这一章节准备写一下shared memory的访问的问题,首先是讲一下shared的memory的两种使用方法,然后讲解一下shared memory的bank conflict的问题,这个是shared

dmjlzygx的专栏 2666

CUDA编程 - 共享内存 - shared memory - 学习记录

1、共享内存是可受用户控制的一级缓存。2、共享内存是基于存储体切换的架构。所以我们必须要解决存储体冲突的问题数据重复利用全局内存合并线程之间有共享数据4、线程访问共享内存的时候需要排队等候5、以行的方式访问全局内存的时候,性能最好6、在GPU上线程块的大小或者线程束的大小可以理想地映射为数据集的大小。对于一个长为N的数据,我们可以最多用N/2个线程来进行。7、共享内存的主要作用是减少对全局内存的访问,或者改善对全局内存的访问模式。

点云算法工程师 2427

【个人记录】CentOS7升级glibc至2.28

由于CentOS7系统已经停止支持, 虽然大部分业务已经迁移至OpenEuler,但仍然有部分业务系统跑在CentOS7上。在尝试部署新业务环境时经常会提示如下错误(以code-server运行需要的node 20为例)node -v为了保障业务可正常运行,通常采用Docker继承好的node环境进行部署,但部分场景还是需要宿主机使用新版glibc版本。本文记录本人一次成功升级glibc(2.17=>2.28)的步骤。

喵呜 3885

GPU显存层级结构详解:从寄存器到HBM3的性能真相

GPU显存层级结构是影响AI模型训练与推理性能的核心硬件基础,它由寄存器、Shared Memory、L2缓存和HBM3全局内存构成多级加速体系,遵循‘越近越快、越远越慢’的物理规律。其设计原理决定了数据搬运效率远比标称显存容量更能制约实际吞吐——例如L2缓存命中率不足将直接导致Tensor Core喂不饱,而Shared Memory配置错误则引发CUDA kernel加载失败。该结构深刻影响PyTorch张量调度、CUDA kernel编写及大模型部署(如Qwen2-7B、FunASR、Ollama)的

weixin_30897079的博客 338

2023电视专用!安卓电子相册APP避坑指南(附开源代码)

本文针对2023年智能电视用户,提供安卓电子相册APP的避坑指南与实战教程。文章详细解析了电视端相册应用的常见广告与兼容性陷阱,并指导用户如何安全获取、部署纯净的APK应用,通过核心设置实现无广告的个性化图片轮播体验,最终打造专属的家庭回忆墙。

weixin_29012677的博客 495

10. CUDA cosnstant使用(一)------GPU的革命

10. CUDA cosnstant使用(一)------GPU的革命 序言:最近的事情无比的多,差点就找不到回家的路了,都快忘记出发的起点的时候,冷静下来,侧夜未眠,事情再多,都要一件一件的做好,做不好的,就不接,我想别人也...

coa1128805的博客 207

【Java】JAVA知识总结浅析

Java是一门功能强大的编程语言,广泛应用于多个领域。Java的编程思想,包括面向过程和面向对象编程,Java的发展历史,各版本的特点,JVM原理,数据类型,Java SE与Java EE的区别,应用场景,JDK与JDBC,Java Web技术,Spring Boot系列,以及MyBatis框架的基本实现,最后还将讨论Java在大数据、人工智能、嵌入式和Android等领域的应用。

wnm23的专栏 5239

Ubuntu开发环境配置——持续更新

本文简要介绍了Ubuntu下的开发环境配置流程和问题分析与解决,更多内容持续更新中。

m0_53365889的博客 2225

GPU技术代际迁移实战指南:从CUDA报错到多架构部署

GPU已从通用并行处理器演进为AI与HPC专用计算引擎,其核心驱动力是Tensor Cores硬件加速与CUDA生态分层演化。理解CUDA驱动、工具包、运行时及框架绑定四层依赖关系,是解决torch.cuda.is_available()失败、nvidia-smi不可用、NCCL超时等高频问题的前提。在大模型训练、边缘推理(如Jetson Orin NX)和国产替代(如昇腾910B)等真实场景中,技术价值体现为确定性低延迟、能效比优化与跨架构可移植性。本文聚焦当下可验证、可复现的GPU开发实践,覆盖环境诊断

343

第六天 界面操作及美化(6.4 使用GUI 套件)

本文介绍如何使用VIPM工具安装DMC GUI套件:首先打开VIPM并连接网络,搜索"UI"找到DMCGUI套件后下载安装;安装完成后即可在VIPM中添加控件并预览效果。该方法同样适用于下载其他UI套件,用户可根据需求设计个性化界面。整个流程包括搜索、下载、安装和使用四个简单步骤,适合快速搭建GUI界面。

weixin_38637405的博客 407

GPU架构深度解析:从CUDA核心到SM调度的硬件真相

GPU并非‘多核CPU’,而是一种基于SIMT架构、以确定性流水线和层级内存为核心的并行计算设备。其计算效率取决于线程组织(warp/block)、资源竞争(寄存器/共享内存)与数据访存模式的协同优化。理解流式多处理器(SM)的资源配额机制、warp divergence对性能的隐性损耗,以及全局内存与共享内存之间的带宽鸿沟,是突破‘能跑但慢’‘显存崩塌’等典型问题的关键。尤其在AI加速场景下,Tensor Core的启用更依赖数据对齐、类型匹配与矩阵维度约束等硬性条件。本文聚焦RTX 4090/A100等

ctk87443的博客 489

1.为什么要学习SIMULINK

用户也可以自定义和创建自己的模块,利用这些模块,用户可以创建层次化的系统模型,可以自上而下或自下而上地阅读模型,也就是说,用户可以查看最顶层的系统,然后通过双击模块进入下层的子系统查看模型,这不仅方便了工程人员的设计,而且可以使自己的模型方块图功能更清晰,结构更合理。此外,利用示波器模块或其他的显示模块,用户可以在仿真运行的同时观察仿真结果,而且还可以在仿真运行期间改变仿真参数,并同时观察改变后的仿真结果,最后的结果数据也可以输出到MATLAB工作区进行后续处理,或利用命令在图形窗口中绘制仿真曲线。

FPGA/MATLAB学习教程/源码/项目合作开发 1547

Autosar PWM模块的配置和使用

在上面的代码中,我们首先配置了PWM的初始化参数,包括频率和分辨率。GPIO Driver:由于PWM信号需要输出到特定的引脚上,因此我们还需要使用GPIO驱动程序来控制引脚的状态。通过合理配置和应用PWM模块,我们可以实现对汽车电子系统中各种功能的精确控制,从而提高汽车性能和用户体验。在实际应用中,我们可以根据需要进行更多的配置和调整,以实现灵活的PWM控制。PWM Driver:这是主要的PWM驱动程序,负责产生PWM信号并输出到相应的引脚上。接下来,我们需要配置PWM模块的参数。

TcData的博客 682

gemma-4-31B-it-qat-w4a16-ct调优清单:temperature=1.0、top_p=0.95、top_k=64官方推荐参数深度解析

gemma-4-31B-it-qat-w4a16-ct 是 Google 面向 Gemma 4 31B 指令微调模型发布的 QAT(量化感知训练)权重检查点,采用 4-bit 权重 + 16-bit 激活(w4a16)压缩格式,可在显存大幅降低的前提下保持接近 bfloat16 的推理质量。官方为它写死了一组"开箱即用"的采样参数:**temperature=1.0、top_p=0.95、top_

gitblog_01075的博客 233

OpenHarmony RK3568 从 32 位切换到 64 位用户空间指南

修改了编译参数(如修复 Q5 后),但 ThinLTO 缓存中保留了旧参数编译的 bitcode。这是一种节省内存的设计(32 位指针占 4 字节 vs 64 位占 8 字节),但在某些场景下需要切换为纯 64 位。切换到 64 位用户空间后,CPU 全程运行在 AArch64 状态,不再需要 AArch32 兼容模式。内核本身已经是 64 位(aarch64),切换用户空间位数不影响内核。(32 位兼容层)在纯 64 位系统中不再需要,可以关闭以减小内核体积。这两个参数对 64 位编译毫无意义,配合。

程小二的博客 690
上一篇: CUDA __global__ function 参数分析
下一篇: 【CUDA比赛】题目征集
OpenHero
博客等级 码龄23年 2276粉丝 226原创
评论 18
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值