深度学习完全攻略!(连载三:GPU加速技术指南)

我只是一个搬运工,CM是Intel自己开发并开源的东东,方便开发者使用他的芯片。所以,也算是给Intel做免费宣传。但是从技术上讲,是没有界限的。本文已同步至公众号,欢迎订阅。

Cm服务端的设计就像C语言一样,又有些类似C++。如果你有一些c/c++的编程基础,对于理解服务端的代码就很容易。

这一节,会从以下几个方面来介绍,数据类型,运算符和库函数来介绍。至于扩展的一些函数,有兴趣的可以自己看看。

 

第一章 数据类型

第一节 简单数据类型

CM支持如下的简单数据类型。

  1. char, unsigned char (uchar), short, unsigned short (ushort)int, unsigned int (uint), float, half(半精度).对于半精度的类型,CM支持IEEE-754兼容16位半精度浮点类型,要启用此类型,请对client代码使用名称空间“half_float”,对变量声明使用关键字“half”。
  2. 另外,对于Gen7+ 的硬件,还适用double-precision floating-point data type (双精度浮点数据类型)
  3. 对于Gen8+的硬件,还适用unsigned long long, long long
  4. 同时,对于Gen8+的硬件,还有svmptr_t类型,表示SVM(共享虚拟内存)指针大小。声明SVM中的结构时,请对指针字段使用svmptr_t。svmptr_t的大小由编译器选项/dcm_ptrsize=32或/dcm_ptrsize=64设置。32和64指的是操作系统的位数。

第二节 复杂数据类型

(1)基本对象

基本对象有两个,vector和matrix.

Vector<type, size>, 很明显,type表示vector中的数据类型,size表示vector的长度。例如,

Vector<int, 8> v;  v这个变量表示这样的一段内存

 

 

 

 

 

 

 

 

Matrix<int, 3, 3> m;  m这个变量表示这样一段内存Matrix<type, rows, columns>,type表示matrix中的数据类型,rows表示这个matrix有多少行,columns表示数据有多少列。例如,

 

 

 

 

 

 

 

 

 


注意:

type必须是上面第一节中的数据类型

Matrix和vector变量的大小必须小于4096个字节。

 

(2)引用对象

顾名思义,就是一个引用。类似于c++中的引用,但是比C++中的引用更简单。这个引用主要针对matrix和vector,对应的就是vector_ref 和matrix_ref。

vector_ref<type, size>表示对某个类型为type,大小为size的一个vector 变量的引用。

matrix_ref<type, rows, columns>表示对某个类型为type,rows行,columns列的一个matrix变量的引用。

例如:

 matrix<short, 4, 8> m;

 vector_ref<short, 8> r(m.row(2));

r就表示对m的第二行的一个引用。

(3)结构体

CM也支持结构体,第一节中的基本类型,以及vector和matrix都可以作为结构体的成员,但是引用类型不可以作为成员。结构体必须是内存对齐的。

(4)Mask

做过图像处理的同学肯定知道mask有什么用。Mask可以是vector,可以是matrix。里面的数据包括0和非0.当把mask和另外一个变量运算时,会起到逻辑运算的效果,例如与、或、非。会在下一章节中举例介绍。

 

(5)其他类型

下面的三个,其实在我的另外一篇博客中有使用。《深度学习完全攻略。(连载二:GPU加速技术指南)》

SurfaceIndex:表示数据端口或其他共享函数中使用的Surface对象。

samplerIndex:表示sampler的对象。

vmeindex:表示在vme函数中使用的vme对象。

CM支持在内核函数中使用surfaceindex的向量,它必须作为内核函数参数从client传递。CM不允许修改、引用或子选择/选择SurfaceIndex向量的操作。例如:下面的surfaceindex是vector的数据类型,同时也必须是client端传过来。

#include <cm/cm.h>

_GENX_MAIN_ void

linear(vector<SurfaceIndex, 2> surf_ids, uint h_pos, uint v_pos, int i, int j)

{

  vector<int,8> in, out;

  // ...

  read(surf_ids(i), h_pos*24, v_pos*6, in);

  //  ...

  write(surf_ids(j), h_pos*24, v_pos*6, out);

}

 

(6)变量限定符

CM支持用“__declspec(genx)” (#define’d to _GENX_) 限定的全局变量,全局变量可以是vector,也可以是matrix。用 “__declspec(genx)”/”__declspec(genx_main)” 限定的函数,可以引用“__declspec(genx)”限定的变量。全局变量是线程私有的,CM不支持对全局变量的初始化。

举个例子:

#include <cm/cm.h>

_GENX_ matrix<float, 2, 2> M;      // global variable declaration

_GENX_ float foo(matrix<float, 4, 4> m)

{

  return cm_sum<float>(m);

}

_GENX_ void bar(vector<float, 16> v, float f)

{

  matrix<float, 4, 4> m1;

  m1 = v + f;

  float s = foo(m1);

  M = m1.select<2, 2, 2, 2>(0, 0) * s;

}

_GENX_MAIN_ void kernel(SurfaceIndex inbuf, SurfaceIndex outbuf,

                        int x_pos, int y_pos)

{

  matrix<float, 4, 4> m;

  vector<float, 16>   v;

  read(inbuf, x_pos, y_pos, m);

  v = m;

  bar(v, 0.5f);

  write(outbuf, x_pos, y_pos, M);

}

 

(7)类型转换

CM的类型转换,就像c/C++中的类型转换一样,可以是同类型之间的数据转换,也可以不同类型之间的转换。举两个例子:

void foo() {

  matrix<float, 8, 4> m1;

  matrix<float, 2, 2> m2;

  vector_ref<float, 4> v1(m1.row(2));

  m2 = v1;

  // ...

}

在这个例子中,m1是个矩阵,拿出来第二行赋值给一个vector的引用。同时,vector又可以用来赋值给一个同长度的matrix。长度的检查,则是由编译器去完成。

 

void foo()

{

  vector<float, 8> f;

  vector<int, 8> i;

  // ...

  f = i;                    // 隐式转换

  f = vector<short, 8>(i);  // 显示类型转换

  // ...

}

不同类型的转换,则是按照C++中的template specialization mechanisms来操作的。

 

(8)一些限定

对于C++中的一些常用特性,比如说,指针,继承,内存分配,静态变量等的,CM在用的时候是有一些限制的,会在后续章节介绍。

 

第二章 运算符

运算符能实现对数据的操作,诸如加减乘除。对于C++中的一些基本的标量运算符,CM也是支持的。同时,CM也提供一些自己重载的运算符。

学过矩阵的同学都知道,如果把普通数组单个元素的运算转换为矩阵的运算,将极大的简化我们的编程效率,而且也可以方便我们分析问题,将逻辑推导和实际编程结合起来。所以你将看到,下面所说的运算符操作,基本上都是对vector和matrix的操作。请时刻牢记这一点。称之为矩阵运算,是不是想到了matlab。

 

第一节 赋值运算

cm支持对vector和matrix中的元素单个逐一赋值,支持相同类型变量之间的直接赋值,也支持vector和matrix不同类型变量的相互赋值,只要他们的数据个数是相同的。举个例子:

  vector<uchar, 2> vi, vo;  // 定义两个变量

  vi(0) = 117;   // 单独赋值

  vi(1) = 231;

  vo = vi;  // vo(0) = 117, vo(1) = 231

 

  matrix<uint, 4, 4>      m1, m2;

  matrix_ref<uint, 4, 4>  m3 = m1;

  char c = '?';

  m1 = m2;  // m2的数据复制给m1,

  m1 = c;   // c复制给m1中的所有元素

  m2 = m3;  // m1中的数据都复制给m2,请注意上面的定义,m3是m1的一个引用

 

第二节  vector和matrix变量的构造

(1)很类似于C++中对象的构造。举个例子

  vector<uchar, 2> vi;

  vi(0) = 117;

  vi(1) = 231;

  vector<uchar, 2> vo(vi); // vo(0) = 117, vo(1) = 231

(2)在有些情况下,如果涉及到数据类型之间的相互转换,就要注意了,很可能数据不是原来的值了。那怎么办呢?Cm提供了“SAT”参数来帮忙解决。举个例子:

  vector<int, 2> vi;

  vector<uchar, 2> vo;

  vi(0) = 155;

  vi(1) = 275;

  vo = vector<uchar, 2>(vi);        // vo(0) = 155, vo(1) = 20

  vo = vector<uchar, 2>(vi, SAT);   // vo(0) = 155, vo(1) = 255

SAT将超出uchar范围的数据设置为此类型的最大值。

(3)另外,这两种数据类型,还提供了成员函数,n_rows(),n_cols(),n_elems()分别返回,行数,列数和总的元素个数。

 

第三节 vector和matrix的初始化

(1)cm允许用数组来初始化vector和matrix,但是目前支持整数(int,short等)类型。后面会支持其他类型。Cm建议用这个方式去初始化。同时也不用担心数组的长度和变量的长度不匹配,即使长度不一样,cm编译器只会按照最小的那个长度来。举个例子:

#include <cm/cm.h>

//定义了多个数组

const short init_v_B[2][4] = {1, 9, 17, 25, 33, 41, 49, 57};

const short init_Table[5][16] = {{4,6,8},{0,1,1,0,0},{8,9,7,6},{23},{45}};

const short init_0_7[8] = {0,1,2,3,4,5,6,7};

extern "C"

_GENX_MAIN_ void test1(SurfaceIndex OUT, int index)

{

  vector<ushort, 8>     v_0_7(init_0_7);  // 用上面的数组去初始化

  matrix<uint, 5, 16>   m_Table(init_Table);

  vector<short, 8>      v_B(init_v_B);  // 强调一下,主要元素个数相同,编译器会处理的。

  // ...

}

(2)同时,CM也提供了内部的初始化函数,举个matrix例子:

#include <cm/cm.h>

#include <cm/cmtl.h> // 要包含这个头文件

_GENX_ void test1()

{

  cm_matrix(m, ushort, 4, 8, 10, 5);  // 定义一个m的matrix,同时初始化。

  // ...

}

语法结构是:cm_matrix(M,T,R,C,I,S);

M – 变量名; T – 元素类型; R - 行数; C - 列数; I – 初始化数值序列的起始值; S – 初始化数值序列步长.比如说上面的例子,m就被初始化为:10,15,20,25.。。。。。

(3)再举个例子:

#include <cm/cm.h>

#include <cm/cmtl.h>

_GENX_ void test1()

{

  cm_vector(v, ushort, 16, 2, 3); // 定义一个v的vector,同时初始化。

  // ...

}

语法结构是:cm_vector(V,T,N,I,S);

V – 变量名; T – 元素类型; N - 个数; I – 初始化序列的起始值; S – 初始化序列的步长

上面的变量就被初始化为:2,5,8.。。。。。

(4)细心的小伙伴肯定注意到了,那如果是已经定义了一个变量,如何初始化呢。你别捉急。Cm提供了复制的函数。比如说,对于vector,有如下的函数:

cmtl::cm_vector_assign(V,I,S);V表示变量名,I表示初始化序列,可以是另外一个变量,S表示步长。举个例子:(V 已经被定义过了,select<>会在后面介绍)

cmtl::cm_vector_assign(v.select<10,1>(2), i, 3);

此时,V就被初始化为:v(2) = i; v(3) = i+3; v(4) = i+6, …

 

第四节 算术运算符

CM也支持这些运算符,是不是似曾相识。没有错,C语言中常用的。+, -, *, /, %, +=, -=, *=

但是在使用的时候,唯一需要注意的就是尺寸的问题。举个例子:

  vector<float, 8>   v1, v2, v3;

  matrix<uint, 2, 4> m1;

  matrix<uint, 4, 2> m2;

  v1 = v2 + v3;

  v1 *= 2.0f;  // 对v1中的每个元素都乘以2.0f

  m2 = m1 - v1;  // 首先,m1.row(0)会减去v1[0-3], m1.row(1)减去v1[4-7],所有的结果在重新组成为m2的形式,不用担心,编译器会帮你解决行数和列数的匹配问题。

CM中处理vector和matrix时,都是以行为单位的。时刻牢记这一点。

 

第五节 移位操作符

Cm支持如下的移位运算符:
>>, <<, >>=, <<=

 

第六节 位操作符

显而易见,这些位操作符也是允许的

&, |, ^, !, &=, |=, ^=, ~

 

第七节 逻辑运算符

支持两个:

&&  与 ||  或

 

第八节 判断

这个抓要是针对vector和matrix而言的。重载了比较运算符。比较是针对向量或者矩阵中的每一个元素。

vector<ushort, size> operator OP (VM x, VMC y);

vector<ushort, size> operator OP (VMC x, VM y);

OP 是 {<; <=; >; >=; ==; !=}其中之一

VM 是vector/vector_ref/matrix/matrix_ref 中的任意一种

VMC 是vector/vector_ref/matrix/matrix_ref/<scalar_type>中的任意一种

举个例子:

  matrix<int, 8, 8>    m1, m2;

  matrix<ushort, 8, 8> m_mask;

  vector<int, 4>       v;

  vector<ushort, 4>    v_mask;

  m_mask = ( m1 >= m2 ); // 会对m1和m2中的每一个进行比较,结果赋值给m_mask.

  v_mask = ( v != 0 );

 

第九节 选择

这个操作主要是方便我们能够访问vector和matrix中各个元素的。

(1)括号式选择()下标都是从0开始的。

对于vector而言,有如下的规则:

operator(ushort i): 返回第i个元素

对于matrix而言,有如下的规则:

operator(ushort i, ushort j): 返回第i行第j列的元素

举个例子:

  vector<uint, 8> v1;

  matrix<uint, 4, 4> m1;

  m1(2,3) = v1(2);

 

(2)中括号式选择[]跟()很类似

对于vector而言,有如下的规则:

Operator[ushort i]: 返回第i个元素

对于matrix而言,有如下的规则:

Operator[ushort i][ushort j]: 返回第i行第j列的元素

举个例子:

  vector<uint, 8> v1;

  matrix<uint, 4, 4> m1;

  matrix<ushort, 4, 4> m2;

  m1[2][3] = v1[2];

  v1[m2[0][3]] += 1;

 

(3)子向量或者子矩阵的选择

select<size, stride>(ushort i=0): 返回一个从第i个元素开始的子vector的引用,size表示选择元素个数,stride表示两个相邻选择元素之间的间距。

select<v_size, v_stride, h_size, h_stride>(ushort i=0, ushort j=0): 返回一个从(i,j)开始的,垂直方向大小为v_size,间隔为v_stride, ,水平方向大小为h_size, 间隔为h_stride的子矩阵。

举个例子:

  vector<int, 8> a;

  vector<int, 4> b;

b=a.select<4,2>(1);//size=4,stride=2,offset=1,a(1),a(3),a(5)和a(7)被复制到b中。

matrix<float, 4, 8>  m1;

  matrix<float, 2, 2>  m2;

  m2 = m1.select<2, 2, 2, 4>(1, 2);

 

(4)选择所有

Cm提供函数,可以选择所有的元素:

Select_all()

举个例子:

#include <cm/cm.h>

template <typename T, uint R, uint C>  // 模板函数

_GENX_ inline void

mult3(matrix_ref<T, R, C> par)

{

  par = par * 3;

}

_GENX_MAIN_ void

kern(matrix<int, 4, 2> p)

{

  matrix<int, 4, 2> m(p); // m = p;

  mult3(m.select_all());  // m = p * 3;

}

(5)间接选择

Cm支持间接选择,iselect(idx),idx是要选择元素的下标。对于matrix而言:iselect(row, col)

举个例子:(vector)

  vector<int, 65> inVector = 0;

  vector<int, 8>  outVector = 0;

  vector<int, 12> tempVector = 0;

  vector<ushort, 12>  idx;

  inVector.select<2,3>(4) = 19; // Here: inVector(4) = 19, inVector(7) = 19

  //Now: inVector = {0,0,0,19,0,0,0,19,0...}

  outVector = inVector.select<8,1>(0);

  //Now: outVector = {0,0,0,19,0,0,0,19}

   idx = 7; idx(0) = 2; idx(2) = 4; idx(3) = 10;

  // Now: idx = {2,7,4,10,7,7,7,7,7,7,7,7}

  tempVector = inVector.iselect(idx);

  // Now: tempVector = {0,19,19,0,19,19,0,0,0,0}

    tempVector = tempVector * 2;

  // Now: tempVector = {0,38,38,0,38,38, ... }

  outVector.select<8,1>(0) += tempVector.select<8,1>(1);

  // Now: outVector = {38,38,0,57,38,38,57,38}

 

再举个例子:(matrix)

  // Here: init_a = {{0, 2}, {4, 6}, {8, 10}, {12, 14}};

  matrix<int, 4, 2> a(init_a);

  // Here: init_b = {0, 2, 3}; init_c = {1, 0, 0}

  vector<int, 3> b(init_b), c(init_c);

  vector<int, 3> out;

  out = a.iselect(b, c); // out = {2, 8, 12}

(6)源区域复制操作

这里主要说明三种用法:

第一种,下面的函数实现对matrix/vector中从(i,j)/(i)开始的连续W个元素的复制操作,并返回一个REP * W长度的vector。就是说相同的内容重复REP次。

replicate<REP, W>( ushort i=0, ushort j=0) )

replicate<REP, W>( ushort i=0 )

第二种,实现对matrix/vector中从(i,j)/(i)开始的,一共REP个连续W个元素的复制操作,并返回一个REP * W长度的vector。每两个W长度的元素之间的步长是VS。如果VS < W, 那么两个W长的块之间肯定是有重叠的。

replicate<REP, VS, W>( ushort i=0, ushort j=0) )

replicate<REP, VS, W>( ushort i=0 )

第三种,直接上英文描述吧。

The following can be used in CM to select/replicate “REP” blocks of “W” sequential elements with a stride of “HS” starting at (i,j)/(i) from the matrix/vector object with each block strided by “VS” elements, and return a new vector of “REP” * “W” length. Selected blocks of “W” elements will overlap if “VS” < “W”.

replicate<REP, VS, W, HS>( ushort i=0, ushort j=0) )

replicate<REP, VS, W, HS>( ushort i=0 )

(7)选择某一行或者某一列

我们在前面已经看到过了,row(1),或者column()

 

第十节 设置成员函数格式

这个就有意思,我们看到,对于不同类型的vector或者matrix之间,如何直接进行赋值操作呢。莫慌,cm提供了两个函数来完成:

对于matrix:format<type, rows, columns>( )

对于vector:format<type>( )

可能你还是没懂,不急,我们举个例子:

  matrix<int, 4, 4>       m1;  //定义m1,

  matrix_ref<char, 4, 16> m2 = m1.format<char, 4, 16>( );  // 把int转换为char,看到没,size变了

  // m2 is a reference to the location of m1

  // interpreted as a matrix 4x16 of chars.

  matrix_ref<int, 2, 8>   m3 = m1.format<int, 2, 8>( );

  // m3 is a reference to the location of m1

  // interpreted as a 2x8 integer matrix.

  // (assuming little endian layout, i.e.

  //  the lowest byte address of element

  //  m1(0,0) is referenced by m2(0,0))

 

  matrix<float, 2, 8>  m4;

  vector_ref<float, 16> v1 = m4.format<float>();

  // v1 is a reference to the location of m4

  // interpreted as a vector of 16 floats.

 

第十一节 合并成员函数

什么意思呢?实际上就是对mask的使用。怎么用呢?一说你就明白了。

第一种,只有一个源操作数的。

void VM::merge(VMC x, int mask)

void VM::merge(VMC x, VM mask)

意思也就是说,如果mask里面的值被置为1,则将x对应的值copy到调用这个函数的vector或者matrix中对应位置处。

举个例子来说明吧。

  matrix<int,2,4>   m, src;

  matrix<uchar,2,4> mask;

  // ...

  m.merge(src, mask);

  // m           src         mask      --->  m

  // 2 2 2 2     4 4 4 4     1 1 0 1         4 4 2 4

  // 2 2 2 2     4 4 4 4     0 1 1 0         2 4 4 2

  vector<int, 4> v1, v2;

  int imask = 0xA;

  // ...

  v1.merge(v2, imask);

  // v1          v2          imask     --->  v1

  // 2 2 2 2     4 4 4 4     0xA: 1010       2 4 2 4

 

第二种,有两个源操作数的。

void VM::merge(VMC x, VMC y, int mask)

void VM::merge(VMC x, VMC y, VM mask)

也就是说,如果mask的值被置为1,则将x对应的值copy到调用这个函数的vector或matrix中,否则将y的值copy过去。举个例子:

  matrix<int,4,2>   m, src1, src2;

  matrix<ushort,4,2> mask;

  // ...

  m.merge(src1, src2, mask);

  // m       src_1   src_2   mask   --->   m

  // 2 2     4 4     8 8     1 0           4 8

  // 2 2     4 4     8 8     1 1           4 4

  // 2 2     4 4     8 8     0 1           8 4

  // 2 2     4 4     8 8     0 0           8 8

 

第十二节 布尔函数

这个也是对mask进行操作的。

(1)ushort vector<ushort, size>::any(void) / ushort matrix<ushort, R, C>::any(void)

这个函数返回1,如果mask中的有一个元素不是0,否则返回0。

(2)ushort vector<ushort, size>::all(void) / ushort matrix<ushort, R, C>::all(void)

这个函数返回1如果所有的元素都是非零,否则返回0。

举个例子:

  matrix<int, 8, 8> m1;

  // Vector mask

  vector<ushort, 64> v_mask = (m1 > 0);

  ushort result = v_mask.any();

  if (result) {

    // At least one value in m1 is > 0

   }

  if (v_mask.all()) {

    // All values in m1 are > 0

 }

 

  // Matrix mask

  matrix<ushort, 8, 8> m_mask = (m1 == 0);

  if (m_mask.all()) {

    // All values in m1 are 0

    // ...

  }

  if ((m1 == 0).all()) {

    // Another way to express the same thing without using an

    // intermediate variable

    // ...

  }

  while ((m1 == 0).any()) {

    // As long as m1 still has a 0, keep looping...

    // ...

  }

 

第十三节 赋值顺序

对于所有的操作符而言,都是先读取操作,然后再执行。不多说,肯定的。

 

第十四节 控制流程

其实在上面的一些例子中,我们已经看到了,cm也是支持条件句的。比如,if-else, do-while,for.

 

好了,第二章的内容基本上就结束了。这一章主要告诉你,如果想编写CM内核,就必须知道可以用那些数据类型,怎么去操作。而且,最常用的就应该是vector和matrix了。

 

下一章,将带大家了解cm中都提供了哪些函数,便于内核函数的编写。

 

 

 

 

 

 

 

发布了77 篇原创文章 · 获赞 150 · 访问量 23万+

猜你喜欢

转载自blog.csdn.net/Aoulun/article/details/89608345
今日推荐