我只是一个搬运工,CM是Intel自己开发并开源的东东,方便开发者使用他的芯片。所以,也算是给Intel做免费宣传。但是从技术上讲,是没有界限的。本文已同步至公众号,欢迎订阅。
Cm服务端的设计就像C语言一样,又有些类似C++。如果你有一些c/c++的编程基础,对于理解服务端的代码就很容易。
这一节,会从以下几个方面来介绍,数据类型,运算符和库函数来介绍。至于扩展的一些函数,有兴趣的可以自己看看。
第一章 数据类型
第一节 简单数据类型
CM支持如下的简单数据类型。
- char, unsigned char (uchar), short, unsigned short (ushort),int, unsigned int (uint), float, half(半精度).对于半精度的类型,CM支持IEEE-754兼容16位半精度浮点类型,要启用此类型,请对client代码使用名称空间“half_float”,对变量声明使用关键字“half”。
- 另外,对于Gen7+ 的硬件,还适用double-precision floating-point data type (双精度浮点数据类型)
- 对于Gen8+的硬件,还适用unsigned long long, long long
- 同时,对于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中都提供了哪些函数,便于内核函数的编写。