深度学习工程化(1)多维图片在内存的高效存储和访问

大多数计算都是关于数据的:分析数据,调整数据,读取和存储数据,生成数据等。深度学习框架也不例外。图像,权重/过滤器,声音和文本需要有效地表示在计算机内存中,以便于以最便捷的方式快速执行操作。

对计算机内存了解的同学应该知道,应用程序看到的内存是线性存储空间(即1D),而图片等数据是多维数据,如何建立多维逻辑表示和线性物理排布的映射关系并高效访问指定的指定的元素?

本文致力于数据格式-一种数据表示形式,它描述了多维数组(nD)如何存储在线性(1D)内存地址空间中以及为什么这对深度学习算法非常重要。

 

数据格式(Data Formats)

 

输入/输出向量(激活)包括通道(也称为特征图)和一个空间域,1D,2D或3D。 空间域与通道一起形成图像。在深度学习算法中,通常将图像分批处理。即使只有一幅图像,我们仍然假设存在一个批次大小(batch size)等于1的批次。因此,激活的总维数为 4D(N,C,H和W)或 5D(N,C,D ,H 和 W)。比如,描述一幅 8幅 RGB 三通道的 100x100 大小彩色图像需要 4D 数据((N=8, C=3, H=W=100)batch_size, 通道数,空间域包括长和宽)。

 

普通数据布局 ( plain data formats)

 

从一个例子开始会更简单。

考虑 batch_size 等于2、16通道和 5 x 4 空间域的 4D 激活。下图给出了逻辑表示。

 

 

为了定义此 4D 张量 (tensor) 中的数据在内存中的布局方式,我们需要定义如何通过以逻辑索引(n,c,h,w)为输入的偏移量函数将其映射到 1D 张量。并将地址位移返回到该值的位置:offset : (int, int, int, int) --> int

 

       NCHW

 

让我们描述一种非常流行的格式  NCHW 张量值在内存中的排列顺序。这种数据格式的逻辑排列顺序和内存中的物理排列顺序是相同的。

其偏移函数为:

       offset_nchw(n,c,h,w)= n * CHW + c * HW + h * W + w

在这种数据格式中,w 是最内层维度,上述逻辑图中 w 表示一行。这意味着同一行中相邻的两个元素在内存中也是相邻的并且相邻的两个元素将共享相同的 n,c 和 h 索引。相反,n是此处的最外层维度,这意味着,如果需要在下一张图像上获取相同的像素(c,h,w),则必须跳过整个图像尺寸C * H * W。实际例子中,比如我有 8 张 100x100 的RGB 图像,则 N=8, C=3, H=100, W=100. 图像在内存中存储顺序为:先按照行排列 w 维,在按照列排列 h 维 这样便完成了一个通道的存储,然后再按照相同的处理方式处理下一个通道,待 RGB 三个通道都存储完毕后再按照相同的逻辑存储第二幅RGB图像。当我需要获取某个位置的存储值时,只需要利用 offset_nchw(n,c,h,w)即可获得相应的物理存储地址。

BVLC * Caffe和 PyTorch 中默认使用 NCHW 数据格式。TensorFlow 也支持这种数据格式。

 

       NHWC

 

NHWC (PyTorch 称其为 Chanel-last )是另一种非常流行的数据格式,它使用以下偏移函数:

      offset_nhwc(n,c,h,w)= n * HWC + h * WC + w * C + c

在这种情况下,最里面的尺寸是通道,然后是宽度、高度,最后是 batch。

对于单个图像(N = 1),此格式与BMP文件格式的工作原理非常相似,在该格式中,图像逐像素保存,每个像素包含所有必需的颜色信息(例如,三个通道用于24bit BMP)。

NHWC数据格式是TensorFlow的默认格式。 

 

       CHWN

 

普通数据布局的最后一个示例是CHWN,它由Neon使用。如果使用适当的批处理大小,则从矢量化角度来看,此布局可能非常有趣,但另一方面,用户不能始终具有良好的批处理大小(例如,在实时推断批处理的情况下,通常为1)。尺寸顺序为(从最内到最外):批处理([c:0]),宽度([c:1]),高度([c:2]),通道([c:3]) 。

CHWN格式的偏移量函数定义为:

          offset_chwn(n,c,h,w)= c * HWN + h * WN + w * N + n

 

实例解读

 

下图展示了不同数据格式下从逻辑表示到内存物理数据排布的映射。理解的关键是内存物理排布从逻辑维度的最内层开始,并且最内层维度中的相邻元素之间在内存中也是相邻的。比如在NCHW下,最内侧为 W 维排列的顺序依次是 000/001/002.... ;而在NHWC中的顺序中,最内侧为 C 维, 因此物理排布为 000/020/040...。[x:1] 表示逻辑排布从最内侧维度切换到下一个维度,比如[a:1] 表示当前行排列完了,接着下一行继续排列。[b:1]表示当前最内侧的C维拍完了接着W维排列。[x:2],[x:3]也类似表示其他的维度切换。如果你还是不理解,就按照数字的序号反复推几遍,也就懂了。

 

普通数据布局的一般形式

 

      步长(Strides)

 

在前面的示例中,数据在内存中保持打包或者密集型(packed or dense),这意味着数值彼此跟随。有时可能有必要在内存中保持数据不连续。例如,有些人可能需要在更大的张量内使用子张量(sub-tensor)。下图显示了尺寸为 rows x columns 的 2D 矩阵的简化情况,物理排布采用行优先格式,其中行具有不平凡的步长(即,不等于列数)。

在这种情况下,一般的偏移函数如下所示:

offset(n, c, h, w) = n * stride_n

+ c * stride_c

+ h * stride_h

+ w * stride_w

请注意,NCHW,NHWC和CHWN格式只是步长格式的特殊情况。例如,对于NCHW,我们有:stride_n = CHW,stride_c = HW,stride_h = W,stride_w = 1。在PyTorch中,有时我们需要保证 tensor 的数据存储在一块连续的内存中, stride 和 shape 信息可以一起用来判断当前的张量在内存中是否是连续存储的(is_contiguous)。

 

块数据布局(Block Formats)

 

普通数据布局具有很大的灵活性,使用起来非常方便。这就是为什么大多数框架和应用程序都使用NCHW或NHWC布局的原因。但是,根据对数据执行的操作,可能会发现从性能角度来看,这些布局不是最佳的。为此 oneDNN引入了块布局格式,这些格式主要是为了在使用向量化指令(AVX512/SSE等),考虑到其特殊性,这里不做介绍,需要的同学可以参考(本篇文章也是在翻译和理解这篇文章的基础上创建。)

Understanding Memory Formats

https://oneapi-src.github.io/oneDNN/dev_guide_understanding_memory_formats.html

                        

    欢迎关注“程序员的大厂之路”微信公众

    更多技术干货持续更新中......                         

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值