opencl与数据格式

1、多维数据

推理引擎中会接触到多种类型的数据格式,比如NHWC、NCHW等,那这些数据格式该如何理解,它们的物理排布又是怎么样的呢?

首先,NHWC四个维度分别是怎么样的呢?

  • H,高度
  • W,宽度
  • C,channel,假设一张图片,宽高很容易理解,图片中的一个像素会有raba等不同分量,C即是指不同的分量,针对一般图片而言,C等于4
  • N,batch,可以简单理解为图片的数量

如果N = 2, C = 16, H = 5, W = 4,那么NCHW、NHWC两种格式的数据排布分别如下:

首先,不管是什么格式的数据,它们的物理排布,其实都是一维数组,就像是二维数组,其实也是一维数组实现的,排布也是一维数组。

其次,不论是何种格式,都是先排最后一个维度的数据,如上图,NHWC是一种比较费解的格式,但它其实也是先取最后一个维度C的16个数据,取完所有的16个C的数据,再把倒数第二个数组W移动一位,继续取所有C,即NHWC取数据,是按 C W H N的逆序取的。

举个例子,假设针对格式为NCHW,范围内的任意nchw,那它在一维数组的哪个位置呢?

ini 复制代码
int stride_n = C * H * W * D, 
stride_c = H * W * D, 
stride_h = W * D, 
stride_w = D;

int index = n * stride_n + c * stride_c + h * stride_h + w

提个小问题,如果要把NCHW的数据转换成NHWC格式的数据,该怎么转呢?其实非常简单,对于索引固定的数据,nchw,它在一维数组中的位置是可知的,那同样的索引,nhwc,在一维数组中的位置也是可知的,赋值即可

2、image

在推理引擎中,要处理多维数据,那么核函数中是使用image更好,还是使用其它类型的数据更好呢?

一般而言,image数据有如下好处:

  • 硬件加速缓存,GPU会为image数据提供纹理缓存
  • 较为高效的内存方式模式,通道打包,一次可以存取4个通道数据

这种特性,比较好处理NCHW这种多维数据。所以一般推理引擎的核函数中,也是用image来处理数据。但它也有缺点,它的缺点就是数据格式较复杂,理解难度高

现在问题来了,要使用image处理张量数据,假设张量数据格式为NHWC,但image只有宽高两个维度,那要怎么对应的呢?

假设张量形状为 [N=2, H=3, W=3, C=8],其逻辑布局如下(以 N=0 为例):

H/W W=0 W=1 W=2
H=0 [C0,C1,C2,C3,C4,C5,C6,C7] ... (共 3x8=24 个通道值)
H=1 ...
H=2 ...
  • 每个 (H,W) 位置有 8 个通道值(C=0~7),内存中是连续的。
  • N 维度 :不同批次的数据在物理内存中是连续存储的(N=0 后紧跟 N=1)。

因为N和H是具有内存连续性的,同时W和C也是有内存连续性的,将N和H合并,W和C合并。

ini 复制代码
image.h = N * H
image.w = W * C / 4

为什么C要除以4呢,因为image天然有ragb四个通道

另外为什么一定要NH合并,WC合并呢,不能是其它数据合并吗?其实主要是从性能上考虑,WC、NH它们在内存上有连续性

注意,因为C/4可能出现0,必须确保C是4的倍数,如果不是,则需要补齐,C/4可以理解为C对4向上取整

N=2, H=3, W=3, C=8 为例,合并之后,image2d_t 的布局如下:

如果已经知道image中的x和y,那么怎么推算张量中的位置呢?

注意,最终的c值无法反推了,因为合并只是合并C4,而不是C,只能反推得到C4。

注意,如果使用image处理张量数据,NHWC的格式更适合image处理。而NCHW不适合image处理,因为image一次性就会取4个通道数据,契合NHWC。

3、最大池化算子实践

最大池化的计算方式比较简单,计算方式如下图所示:

现在要使用opencl的方式实现,核函数使用image处理数据,输入的数据的格式为NHWC

java 复制代码
__kernel void max_pooling(
    __read_only image2d_t input,
    __write_only image2d_t output,
    int out_img_width, int out_img_height,
    int width, int height,
    int kW, int kH,
    int strideW, int strideH,
    int outW, int outH, int channel_4
) {
    int x = get_global_id(0);
    int y = get_global_id(1);
    
    // 边界检查
    if (x >= out_img_width || y >= out_img_height) {
        return;
    }
    
    int w_out = x / channel_4;
    int c4 = x % channel_4;
    int h_out = y % outH;
    int n = y / outH;

    int h0 = h_out * strideH;
    int w0 = w_out * strideW;

    printf("x %d, y = %d, w_out = %d, c4 = %d, h_out = %d, n = %d\n", x, y, w_out, c4, h_out, n);

    float4 maxv = (float4)(-FLT_MAX);

    const sampler_t sampler = CLK_NORMALIZED_COORDS_FALSE | 
                             CLK_ADDRESS_CLAMP | 
                             CLK_FILTER_NEAREST;
    for(int kh = 0; kh < kH; ++kh) {
        for(int kw = 0; kw < kW; ++kw) {
            int h = h0 + kh;
            int w = w0 + kw;
            int scrX = c4 * width + w;
            int scry = n * height + h;
            float4 v = read_imagef(input, sampler, (int2)(scrX, scry));
            maxv = max(maxv, v);
        }
    }

    int dst_x = c4 * outW + w_out;
    int dst_y = n * outH + h_out;
    write_imagef(output, (int2)(dst_x, dst_y), maxv);
}
  • 首先,注意上面根据x、y反推张量位置的计算方式,和此前说法一致
  • h0和w0,是输出窗口的起始位置,关于最大池化位置计算公式,大家可以自行百度,通过上述公式可以得到当前池化窗口的起始w和h值
  • 然后就是遍历池化窗口宽高了,遍历找到当前窗口的最大值,找到之后再写入对应的位置
  • 写入位置,也是image的位置,这里又涉及到从张量位置转换为x/y值
相关推荐
飞哥数智坊17 小时前
GPT-5-Codex 发布,Codex 正在取代 Claude
人工智能·ai编程
倔强青铜三17 小时前
苦练Python第46天:文件写入与上下文管理器
人工智能·python·面试
虫无涯18 小时前
Dify Agent + AntV 实战:从 0 到 1 打造数据可视化解决方案
人工智能
Dm_dotnet20 小时前
公益站Agent Router注册送200刀额度竟然是真的
人工智能
算家计算20 小时前
7B参数拿下30个世界第一!Hunyuan-MT-7B本地部署教程:腾讯混元开源业界首个翻译集成模型
人工智能·开源
机器之心21 小时前
LLM开源2.0大洗牌:60个出局,39个上桌,AI Coding疯魔,TensorFlow已死
人工智能·openai
Juchecar1 天前
交叉熵:深度学习中最常用的损失函数
人工智能
林木森ai1 天前
爆款AI动物运动会视频,用Coze(扣子)一键搞定全流程(附保姆级拆解)
人工智能·aigc
聚客AI1 天前
🙋‍♀️Transformer训练与推理全流程:从输入处理到输出生成
人工智能·算法·llm
BeerBear1 天前
【保姆级教程-从0开始开发MCP服务器】一、MCP学习压根没有你想象得那么难!.md
人工智能·mcp