CUDA学习笔记(十三) Shared Memory

CUDA SHARED MEMORY

shared memory在之前的博文有些介绍,这部分会专门讲解其内容。在global Memory部分,数据对齐和连续是很重要的话题,当使用L1的时候,对齐问题可以忽略,但是非连续的获取内存依然会降低性能。依赖于算法本质,某些情况下,非连续访问是不可避免的。使用shared memory是另一种提高性能的方式。

GPU上的memory有两种:

· On-board memory

· On-chip memory

global memory就是一块很大的on-board memory,并且有很高的latency。而shared memory正好相反,是一块很小,低延迟的on-chip memory,比global memory拥有高得多的带宽。我们可以把他当做可编程的cache,其主要作用有:

· An intra-block thread communication channel 线程间交流通道

· A program-managed cache for global memory data可编程cache

· Scratch pad memory for transforming data to improve global memory access patterns

本文主要涉及两个例子作解释:reduction kernel,matrix transpose kernel。

shared memory(SMEM)是GPU的重要组成之一。物理上,每个SM包含一个当前正在执行的block中所有thread共享的低延迟的内存池。SMEM使得同一个block中的thread能够相互合作,重用on-chip数据,并且能够显著减少kernel需要的global memory带宽。由于APP可以直接显式的操作SMEM的内容,所以又被称为可编程缓存。

由于shared memory和L1要比L2和global memory更接近SM,shared memory的延迟比global memory低20到30倍,带宽大约高10倍。

image

当一个block开始执行时,GPU会分配其一定数量的shared memory,这个shared memory的地址空间会由block中的所有thread 共享。shared memory是划分给SM中驻留的所有block的,也是GPU的稀缺资源。所以,使用越多的shared memory,能够并行的active就越少。

关于Program-Managed Cache:在C语言编程里,循环(loop transformation)一般都使用cache来优化。在循环遍历的时候使用重新排列的迭代顺序可以很好利用cache局部性。在算法层面上,我们需要手动调节循环来达到令人满意的空间局部性,同时还要考虑cache size。cache对于程序员来说是透明的,编译器会处理所有的数据移动,我们没有能力控制cache的行为。shared memory则是一个可编程可操作的cache,程序员可以完全控制其行为。

Shared Memory Allocation

我们可以动态或者静态的分配shared Memory,其声明即可以在kernel内部也可以作为全局变量。

其标识符为:__shared__。

下面这句话静态的声明了一个2D的浮点型数组:

__shared__ float tile[size_y][size_x];

如果在kernel中声明的话,其作用域就是kernel内,否则是对所有kernel有效。如果shared Memory的大小在编译器未知的话,可以使用extern关键字修饰,例如下面声明一个未知大小的1D数组:

extern __shared__ int tile[];

由于其大小在编译器未知,我们需要在每个kernel调用时,动态的分配其shared memory,也就是最开始提及的第三个参数:

kernel<<<grid, block, isize * sizeof(int)>>>(...)

应该注意到,只有1D数组才能这样动态使用。

Shared Memory Banks and Access Mode

之前博文对latency和bandwidth有了充足的研究,而shared memory能够用来隐藏由于latency和bandwidth对性能的影响。下面将解释shared memory的组织方式,以便研究其对性能的影响。

Memory Banks

为了获得高带宽,shared Memory被分成32(对应warp中的thread)个相等大小的内存块,他们可以被同时访问。不同的CC版本,shared memory以不同的模式映射到不同的块(稍后详解)。如果warp访问shared Memory,对于每个bank只访问不多于一个内存地址,那么只需要一次内存传输就可以了,否则需要多次传输,因此会降低内存带宽的使用。

Bank Conflict

当多个地址请求落在同一个bank中就会发生bank conflict,从而导致请求多次执行。硬件会把这类请求分散到尽可能多的没有conflict的那些传输操作 里面,降低有效带宽的因素是被分散到的传输操作个数。

warp有三种典型的获取shared memory的模式:

· Parallel access:多个地址分散在多个bank。

· Serial access:多个地址落在同一个bank。

· Broadcast access:一个地址读操作落在一个bank。

Parallel access是最通常的模式,这个模式一般暗示,一些(也可能是全部)地址请求能够被一次传输解决。理想情况是,获取无conflict的shared memory的时,每个地址都在落在不同的bank中。

Serial access是最坏的模式,如果warp中的32个thread都访问了同一个bank中的不同位置,那就是32次单独的请求,而不是同时访问了。

Broadcast access也是只执行一次传输,然后传输结果会广播给所有发出请求的thread。这样的话就会导致带宽利用率低。

下图是最优情况的访问图示:

image

下图一种随机访问,同样没有conflict:

image

下图则是某些thread访问到同一个bank的情况,这种情况有两种行为:

· Conflict-free broadcast access if threads access the same address within a bank

· Bank conflict access if threads access different addresses within a bank

image

Access Mode

根据不同的CC版本,bank的配置也不同,具体为:

· 4 bytes for devices of CC 2.x

· 8 bytes for devices of CC3.x

对于Fermi,一个bank是4bytes。每个bank的带宽是32bits每两个cycle。连续的32位字映射到连续的bank中,也就是说,bank的索引和shared memory地址的映射关系如下:

bank index = (byte address ÷ 4 bytes/bank) % 32 banks

下图是Fermi的地址映射关系,注意到,bank中每个地址相差32,相邻的word分到不同的bank中以便使warp能够获得更多的并行获取内存操作(获取连续内存时,连续地址分配到了不同bank中)。

image

当同一个warp的两个thread要获取同一个地址(注意是同一个地址还是同一个bank)的时候并不发生bank conflict。对于读操作,会用一次transaction获得结果后广播给所有请求,当写操作时,只有一个thread会真正去写,但是哪个thread执行了写是无法知道的(undefined)。

在8bytes模式中,同理4bytes,连续的64-bits字会映射到连续的bank。每个bank带宽是64bite/1个clock。其映射关系公式:

bank index = (byte address ÷ 8 bytes/bank) % 32 banks

这里,如果两个thread访问同一个64-bit中的任意一个两个相邻word(1byte)也不会导致bank conflict,因为一次64-bit(bank带宽64bit/cycle)的读就可以满足请求了。也就是说,同等情况下,64-bit模式一般比32-bit模式更少碰到bank conflict。

下图是64-bit的关系图。尽管word0和word32都在bank0中,同时读这两个word也不会导致bank conflict(64-bit/cycle):

image

下图是64-bit模式下,conflict-free的情况,每个thread获取不同的bank:

image

下图是另一种conflict-free情况,两个thread或获取同一个bank中的word:

image

下图红色箭头是bank conflict发生的情况:

image

Memory Padding

memory padding是一种避免bank conflict的方法,如下图所示,所有的thread分别访问了bank0的五个不同的word,这时就会导致bank conflict,我们采取的方法就是在每N(bank数目)个word后面加一个word,这样就如下面右图那样,原本bank0的每个word转移到了不同的bank中,从而避免了bank conflict。

image

增加的这写word不会用来存储数据,其唯一的作用就是移动原始bank中的word,使用memory padding会导致block可获得shared memory中有用的数量减少。还有就是,要重新计算数组索引来获取正确的数据元素。

Access Mode Configuration

对Kepler来说,默认情况是4-byte模式,可以用下面的API来查看:

cudaError_t cudaDeviceGetSharedMemConfig(cudaSharedMemConfig *pConfig);

返回结果放在pConfig中,其结果可以是下面两种:

cudaSharedMemBankSizeFourByte

cudaSharedMemBankSizeEightByte

可以使用下面的API来设置bank的大小:

cudaError_t cudaDeviceSetSharedMemConfig(cudaSharedMemConfig config);

bank的配置参数如下三种:

cudaSharedMemBankSizeDefault

cudaSharedMemBankSizeFourByte

cudaSharedMemBankSizeEightByte

在其启动不同的kernel之间修改bank配置会有一个隐式的device同步。修改shared memory的bank大小不会增加shared memory的利用或者影响kernel的Occupancy,但是对性能是一个主要的影响因素。一个大的bank会产生较高的带宽,但是鉴于不同的access pattern,可能导致更多的bank conflict。

Synchronization

因为shared Memory可以被同一个block中的不同的thread同时访问,当同一个地址的值被多个thread修改就导致了inter-thread conflict,所以我们需要同步操作。CUDA提供了两类block内部的同步操作,即:

· Barriers

· Memory fences

对于barrier,所有thread会等待其他thread到达barrier point;对于Memory fence,所有thread会阻塞到所有修改Memory的操作对其他thread可见,下面解释下CUDA需要同步的主要原因:weakly-ordered。

Weakly-Ordered Memory Model

现代内存架构有非常宽松的内存模式,也就是意味着,Memory的获取不必按照程序中的顺序来执行。CUDA采用了一种叫做weakly-ordered Memory model来获取更激进的编译器优化。

GPU thread写数据到不同的Memory的顺序(比如shared Memory,global Memory,page-locked host memory或者另一个device上的Memory)同样没必要跟程序里面顺序呢相同。一个thread的读操作的顺序对其他thread可见时也可能与实际上执行写操作的thread顺序不一致。

为了显式的强制程序以一个确切的顺序运行,就需要用到fence和barrier。他们也是唯一能保证kernel对Memory有正确的行为的操作。

Explicit Barrier

同步操作在我们之前的文章中也提到过不少,比如下面这个:

void __syncthreads();

__syncthreads就是作为一个barrier point起作用,block中的thread必须等待所有thread都到达这个point后才能继续下一步。这也保证了所有在这个point之前获取global Memory和shared Memory的操作对同一个block中所有thread可见。__syncthreads被用来协作同一个block中的thread。当一些thread获取Memory相同的地址时,就会导致潜在的问题(读后写,写后读,写后写)从而引起未定义行为状态,此时就可以使用__syncthreads来避免这种情况。

使用__syncthreads要相当小心,只有在所有thread都会到达这个point时才可以调用这个同步,显而易见,如果同一个block中的某些thread永远都到达该点,那么程序将一直等下去,下面代码就是一种错误的使用方式:

if (threadID % 2 == 0) {__syncthreads();} else {__syncthreads();
}        

Memory Fence

这种方式保证了任何在fence之前的Memory写操作对fence之后thread都可见,也就是,fence之前写完了,fence之后其它thread就都知道这块Memory写后的值了。fence的设置范围比较广,分为:block,grid和system。

可以通过下面的API来设置fence:

void __threadfence_block();

看名字就知道,这个函数是对应的block范围,也就是保证同一个block中thread在fence之前写完的值对block中其它的thread可见,不同于barrier,该function不需要所有的thread都执行。

下面是grid范围的API,作用同理block范围,把上面的block换成grid就是了:

void __threadfence();

下面是system的,其范围针对整个系统,包括device和host:

void __threadfence_system();

Volatile Oualifier

声明一个使用global Memory或者shared Memory的变量,用volatile修饰符来修饰该变量的话,会组织编译器做一个该变量的cache的优化,使用该修饰符后,编译器就会认为该变量可能在某一时刻被别的thread改变,如果使用cache优化的话,得到的值就缺乏时效,因此使用volatile强制每次都到global 或者shared Memory中去读取其绝对有效值。

CHECKING THE DATA LAYOUT OF SHARED MEMORY

该部分会试验一些使用shared Memory的例子,包括以下几个方面:

· 方阵vs矩阵数组

· Row-major vs column-major access

· 静态vs动态shared Memory声明

· 全局vs局部shared Memory

· Memory padding vs no Memory padding

我们在设计使用shared Memory的时候应该关注下面的信息:

· Mapping data elements across Memory banks

· Mapping from thread index to shared Memory offset

搞明白这两点,就可以掌握shared Memory的使用了,从而构建出牛逼的代码。

Square Shared Memory

下图展示了一个每一维度有32个元素并以row-major存储在shared Memory,图的最上方是该矩阵实际的一维存储图示,下方的逻辑的二维shared Memory:

image

我们可以使用下面的语句静态声明一个2D的shared Memory变量:

__shared__ int tile[N][N];

可以使用下面的方式来数据,相邻的thread获取相邻的word:

tile[threadIdx.y][threadIdx.x]

tile[threadIdx.x][threadIdx.y]

上面两种方式哪个更好呢?这就需要注意thread和bank的映射关系了,我们最希望看到的是,同一个warp中的thread获取的是不同的bank。同一个warp中的thread可以使用连续的threadIdx.x来确定。不同bank中的元素同样是连续存储的,以word大小作为偏移。因此次,最好是让连续的thread(由连续的threadIdx.x确定)获取shared Memory中连续的地址,由此得知,

tile[threadIdx.y][threadIdx.x]应该展现出更好的性能以及更少的bank conflict。

Accessing Row-Major versus Column-Major

假设我们的grid有2D的block(32,32),定义如下:

#define BDIMX 32
#define BDIMY 32
dim3 block(BDIMX,BDIMY);
dim3 grid(1,1);

我们对这个kernel有如下两个操作:

· 将thread索引以row-major写到2D的shared Memory数组中。

· 从shared Memory中读取这些值并写入到global Memory中。

kernel代码:

复制代码

__global__ void setRowReadRow(int *out) {// static shared memory__shared__ int tile[BDIMY][BDIMX];// 因为block只有一个unsigned int idx = threadIdx.y * blockDim.x + threadIdx.x;// shared memory store operationtile[threadIdx.y][threadIdx.x] = idx;// 这里同步是为了使下面shared Memory的获取以row-major执行//若有的线程未完成,而其他线程已经在读shared Memory。。。__syncthreads();// shared memory load operationout[idx] = tile[threadIdx.y][threadIdx.x] ;
}                            

复制代码

观察代码可知,我们有三个内存操作:

· 向shared Memory存数据

· 从shared Memor取数据

· 向global Memory存数据

因为在同一个warp中的thread使用连续的threadIdx.x来检索title,该kernel是没有bank conflict的。如果交换上述代码threadIdx.y和threadIdx.x的位置,就变成了column-major的顺序。每个shared Memory的读写都会导致Fermi上32-way的bank conflict或者在Kepler上16-way的bank conflict。

复制代码

__global__ void setColReadCol(int *out) {// static shared memor__shared__ int tile[BDIMX][BDIMY];// mapping from thread index to global memory indexunsigned int idx = threadIdx.y * blockDim.x + threadIdx.x;// shared memory store operationtile[threadIdx.x][threadIdx.y] = idx;// wait for all threads to complete__syncthreads();// shared memory load operationout[idx] = tile[threadIdx.x][threadIdx.y];
}            

复制代码

编译运行:

$ nvcc checkSmemSquare.cu –o smemSquare
$ nvprof ./smemSquare

在Tesla K40c(4-byte模式)上的结果如下,正如我们所想的,row-major表现要出色:

./smemSquare at device 0 of Tesla K40c with Bank Mode:4-byte
<<< grid (1,1) block (32,32)>>
Time(%) Time Calls Avg Min Max Name
13.25% 2.6880us 1 2.6880us 2.6880us 2.6880us setColReadCol(int*)
11.36% 2.3040us 1 2.3040us 2.3040us 2.3040us setRowReadRow(int*)

然后使用nvprof的下面的两个参数来衡量相应的bank-conflict:

shared_load_transactions_per_request

shared_store_transactions_per_request

结果如下(8 bytes模式,4 bytes应该是32),row-major只有一次transaction:

复制代码

Kernel:setColReadCol (int*)
1 shared_load_transactions_per_request 16.000000
1 shared_store_transactions_per_request 16.000000
Kernel:setRowReadRow(int*)
1 shared_load_transactions_per_request 1.000000
1 shared_store_transactions_per_request 1.000000
Writing Row-Major and Reading Column-Major

复制代码

本节的kernel实现以row-major写shared Memory,以Column-major读shared Memory,下图指明了这两种操作的实现:

image

kernel代码:

复制代码

__global__ void setRowReadCol(int *out) {// static shared memory__shared__ int tile[BDIMY][BDIMX];// mapping from thread index to global memory indexunsigned int idx = threadIdx.y * blockDim.x + threadIdx.x;// shared memory store operationtile[threadIdx.y][threadIdx.x] = idx;// wait for all threads to complete__syncthreads();// shared memory load operationout[idx] = tile[threadIdx.x][threadIdx.y];
}                        

复制代码

查看nvprof结果:

Kernel:setRowReadCol (int*)
1 shared_load_transactions_per_request 16.000000
1 shared_store_transactions_per_request 1.000000

写操作是没有conflict的,读操作则引起了一个16次的transaction。

Dynamic Shared Memory

正如前文所说,我们可以全局范围的动态声明shared Memory,也可以在kernel内部动态声明一个局部范围的shared Memory。注意,动态声明必须是未确定大小一维数组,因此,我们就需要重新计算索引。因为我们将要以row-major写,以colu-major读,所以就需要保持下面两个索引值:

· row_idx:1D row-major 内存的偏移

· col_idx:1D column-major内存偏移

kernel代码:

复制代码

__global__ void setRowReadColDyn(int *out) {// dynamic shared memoryextern __shared__ int tile[];// mapping from thread index to global memory indexunsigned int row_idx = threadIdx.y * blockDim.x + threadIdx.x;unsigned int col_idx = threadIdx.x * blockDim.y + threadIdx.y;// shared memory store operationtile[row_idx] = row_idx;// wait for all threads to complete__syncthreads();// shared memory load operationout[row_idx] = tile[col_idx];
}            

复制代码

kernel调用时配置的shared Memory:

setRowReadColDyn<<<grid, block, BDIMX * BDIMY * sizeof(int)>>>(d_C);

查看transaction:

Kernel: setRowReadColDyn(int*)
1 shared_load_transactions_per_request 16.000000
1 shared_store_transactions_per_request 1.000000

该结果和之前的例子相同,不过这里使用的是动态声明。

Padding Statically Declared Shared Memory

直接看kernel代码:

复制代码

__global__ void setRowReadColPad(int *out) {// static shared memory__shared__ int tile[BDIMY][BDIMX+IPAD];// mapping from thread index to global memory offsetunsigned int idx = threadIdx.y * blockDim.x + threadIdx.x;// shared memory store operationtile[threadIdx.y][threadIdx.x] = idx;// wait for all threads to complete__syncthreads();// shared memory load operationout[idx] = tile[threadIdx.x][threadIdx.y];
}                            

复制代码

改代码是setRowReadCol的翻版,查看结果:

Kernel: setRowReadColPad(int*)
1 shared_load_transactions_per_request 1.000000
1 shared_store_transactions_per_request 1.000000

正如期望的那样,load的bank_conflict已经消失。在Fermi上,只需要加上一列就可以解决bank-conflict,但是在Kepler上却不一定,这取决于2D shared Memory的大小,因此对于8-byte模式,可能需要多次试验才能得到正确结果。

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

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

相关文章

基于Pix4D使用无人机光学影像制作正射影像(DOM)和数字表面模型(DSM) 操作步骤

基于Pix4D使用无人机光学影像制作正射影像&#xff08;DOM&#xff09;和数字表面模型&#xff08;DSM&#xff09; 操作步骤 0. 前言1.获取无人机光学影像2.DOM和DSM3.操作步骤3.1 初始界面3.2 新建项目3.3查看处理过程报告3.4查看处理进度和成果 4.在ArcMap中打开DSM和DOM 0.…

django基于Python的房价预测系统+爬虫+大屏可视化分析

欢迎大家点赞、收藏、关注、评论 文章目录 前言一、项目介绍二、开发环境三、功能需求分析1 数据采集功能设计2数据管理功能设计3爬虫功能需求分析4 数据可视化功能需求分析数据库表的设计 四、核心代码五、效果图六、文章目录 前言 房价是一个国家经济水平的重要体现&#xff…

找不到mfc140u.dll无法继续执行此代码的5个修复方法分享

是使用计算机的过程中&#xff0c;我们经常会遇到各种各样问题&#xff0c;其中丢失“mfc140u dll”&#xff08;动态链接库&#xff09;是最常见的一种。DLL文件是一种可在多个程序之间共享的代码库&#xff0c;它可以被应用程序在运行时动态加载和卸载。而“mfc140u dll”则是…

熟练使用 Redis 的五大数据结构:Java 实战教程

入门 入门阶段主要记住 Redis 的命令&#xff0c;熟练使用 Redis 的 5 大数据结构就可以了。 如果没有 Redis 环境&#xff0c;可以直接通过这个网址https://try.redis.io/&#xff0c;很赞&#xff0c;它会给你模拟一个在线的环境可供你尽情使用&#xff01; 熟练使用Redis的…

变分贝叶斯深度学习综述

**©PaperWeekly 原创 作者 |**薛博阳 **单位 |**香港中文大学 **研究方向 |**语言模型 引言 近年来&#xff0c;贝叶斯深度学习&#xff08;Bayesian Deep Learn-ing&#xff09;在诸多领域得到广泛关注应用&#xff0c;效果显著。本文将针对贝叶斯深度学习框架进行系…

测试老鸟总结,Allure测试报告-自动化测试详解,惊险避坑...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、Allure安装教程…

实战:打造一个开箱即用的超丝滑超漂亮hexo博客网站-v4-(通过百度网盘同步空间来同步source核心数据)

实战&#xff1a;打造一个开箱即用的超丝滑超漂亮hexo博客网站-v4-(通过百度网盘同步空间来同步source核心数据) 目录 文章目录 实战&#xff1a;打造一个开箱即用的超丝滑超漂亮hexo博客网站-v4-(通过百度网盘同步空间来同步source核心数据)目录写在前面本次更新方案背景方案官…

Homeassistant docker配置

Homeassistant docker配置 【说明】本系列为自用教程&#xff0c;记录以便下次使用 【背景】一台J1900 4G64G的小主机&#xff0c;安装了OP系统&#xff0c;里面自带了Docker。为实现Homeassistant&#xff08;简称HA&#xff09;控制智能家居设备&#xff0c;进行如下配置。 【…

汽车屏类产品(三):抬头显示Head-Up Display(HUD)

前言 你的下一台车,一定要考虑加装一个HUD。 汽车抬头显示器或汽车抬头显示器(也称为汽车HUD)是任何透明的显示器,它可以在汽车中显示数据,而不需要用户将视线从平时的视角移开。这个名字的由来源于飞行员能够在头部“向上”并向前看的情况下查看信息,而不是向下倾斜查…

[swift刷题模板] 树状数组(BIT/FenwickTree)

[TOC]([swift刷题模板] 树状数组(BIT/FenwickTree) ) 一、 算法&数据结构 1. 描述 [python刷题模板] 树状数组 二、 模板代码 1. 单点赋值(增加)&#xff0c;区间求和(PURQ) 例题: 307. 区域和检索 - 数组可修改 class BIT {var c: [Int]var n: Int init(_ n: Int){c…

OpenCV+QT实现的数字图像处理算法合集

源码下载地址&#xff1a; 基于OpenCV和QT的图像处理源码 图像预处理 灰度处理 灰度直方图 灰度均衡 梯度锐化 Laplace锐化 边缘检测 Roberts Sobel Laplace Prewitt canny Krisch 噪声 椒盐噪声 高斯噪声 滤波 均值滤波 中值滤波 双边滤波 形态学滤波 高斯滤波 图像变…

java--自增自减运算符

1.自增自减运算符 注意&#xff1a;、--只能操作变量&#xff0c;不能操作字面量的。 2.自增自减的使用注意事项 1.、--如果不是单独使用(如果在表达式中、或者同时有其它操作)&#xff0c;放在变量前后会存在明显区别 1.1放在变量前面&#xff0c;先对变量进行1、-1&#xff…

内网穿透的应用-如何通过TortoiseSVN+内网穿透,实现公网提交文件到内网SVN服务器?

文章目录 前言1. TortoiseSVN 客户端下载安装2. 创建检出文件夹3. 创建与提交文件4. 公网访问测试 前言 TortoiseSVN是一个开源的版本控制系统&#xff0c;它与Apache Subversion&#xff08;SVN&#xff09;集成在一起&#xff0c;提供了一个用户友好的界面&#xff0c;方便用…

北邮22级信通院数电:Verilog-FPGA(6)第六周实验:全加器

北邮22信通一枚~ 跟随课程进度更新北邮信通院数字系统设计的笔记、代码和文章 持续关注作者 迎接数电实验学习~ 获取更多文章&#xff0c;请访问专栏&#xff1a; 北邮22级信通院数电实验_青山如墨雨如画的博客-CSDN博客 先抄作业&#xff01;&#xff01;&#xff01;&am…

内衣洗衣机有必要买吗?口碑好的小型洗衣机测评

在近年以来&#xff0c;由于人们对健康的认识和生活质量的不断改善&#xff0c;使得内衣洗衣机这一类的产品在近年来得到了飞速的发展&#xff0c;洗烘一体机、洗烘套装的价格总体下降&#xff0c;功能和性能都得到了改善&#xff0c;往往更多的用户会选择一台或者多台洗衣机来…

qwen大模型,推理速度慢,单卡/双卡速度慢,flash-attention安装,解决方案

场景 阿里的通义千问qwen大模型&#xff0c;推理速度慢&#xff0c;单卡/双卡速度慢。 详细&#xff1a; 1、今日在使用qwen-14b的float16版本进行推理&#xff08;BF16/FP16) 1.1 在qwen-14b-int4也会有同样的现象 2、使用3090 24G显卡两张 3、模型加载的device是auto&#x…

Cross-Modal Joint Embedding with Diverse Semantics

计算两个嵌入之间的相似度得分&#xff0c;然后利用损失函数进行联合嵌入损失最小化优化并更新参数 辅助信息 作者未提供代码

上门预约洗鞋小程序开发;

上门洗鞋小程序服务小程序是一款方便用户与服务提供者进行交流和预约的平台&#xff0c;覆盖多个行业&#xff0c;包括家政清洁、洗衣洗鞋&#xff0c;维修服务等&#xff0c;满足用户在生活中各种需求的上门服务。用户可以在小程序中选择服务项目、预约时间&#xff0c;服务人…

Android Termux安装MySQL,通过内网穿透实现公网远程访问

&#x1f525;博客主页&#xff1a; 小羊失眠啦. &#x1f516;系列专栏&#xff1a; C语言、Linux、Cpolar ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 文章目录 前言1.安装MariaDB2.安装cpolar内网穿透工具3. 创建安全隧道映射mysql4. 公网远程连接5. 固定远程连接地址 前…

2022年京东双11母婴品类数据回顾

母婴产品作为部分家庭的刚需&#xff0c;双11期间的行业热度也节节攀升&#xff0c;2022年双11期间&#xff0c;行业中不少品类赛道势头猛进。下面&#xff0c;鲸参谋带大家一起来回顾2022年双11期间母婴行业大盘及母婴重点细分赛道的销售表现。 母婴行业大盘 2022年双11期间&a…