自定义算子高性能开发
在计图中,一共有三种方法来开发自定义的算子:

  1. 使用元算子进行组合。
  2. 使用Code算子开发自定义算子。
  3. 使用计图编译器编译自定义的模块和custom op。
    其中,元算子开发是最为简单的, 但不免有些情况存在元算子表达能力不足。可以使用Code算子进行开发,Code算子在保持了开发的便捷性,还具有很高的可定制性和性能。和方法3相比,Code算子的开发更加简单,非常适合用户构建模型中的创新算子。
    本文主要介绍Code算子,关于元算子和自定义模块,参考文档:
    • 使用元算子开发卷积
    • 使用计图编译器编译自定义的模块和custom op
    Code算子是一个基于高性能语言的动态编译算子,允许用户直接在Python中内联C++/CUDA代码,只需要寥寥数行代码,就可以完成高性能的自定义算子开发,降低用户开发自定义算子的难度。
    Code 算子的输入参数
    使用Python的help命令(help(jt.code)),可以看到文档如下:
    @param[in] shape 输出的形状, a integer array
    @param[in] dtype 输出的数据类型
    @param[in] inputs 一个计图变量数组
    @param[in] cpu_src CPU前向代码字符串,内建变量包括:

    • in{x}, in{x}_shape{y}, in{x}_stride{y}, in{x}_type, in{x}_p, @in0(…)
    • out{x}, out{x}_shape{y}, out{x}_stride{y}, out{x}_type, out{x}_p, @out0(…)
    • out, out_shape{y}, out_stride{y}, out_type, out_p, @out(…)
      @param[in] cpu_header CPU头文件字符串
      @param[in] cuda_src CUDA 前向代码字符串,和上述参数具有同样的内建变量。
      @param[in] cuda_header CUDA头文件字符串。
      可以看到,用户需要提供Code算子的输入,输出的形状和类型,以及对应的代码。计图会通过编译缓存器,让相同的代码只编译一次。如果希望最大化Code算子的性能,尽量保证Code算子的代码不会出现过多变种。在Code算子的代码中,用户可以使用内建变量,访问计图的变量。下面将用若干个实例,来介绍Code算子的使用。
      实例1:CPU算子以及导数
      下面的实例中,首先生成了一个随机的长度为10的变量a,然后计算了2a22a^22a2 和对应的导数4a4a4a,在这个例子中使用了@out, @in0,这种C++中没有的语法,这种语法目的是给用户提供方便的访问计图变量的接口。这种语法在后端会被翻译成C++可以识别的语法。
      from jittor import Function
      import jittor as jt

class Func(Function):
def execute(self, x):
self.save_vars = x
return jt.code(x.shape, x.dtype, [x],
cpu_src=’’’
for (int i=0; i<in0_shape0; i++)
@out(i) = @in0(i)*@in0(i)*2;
‘’’)

def grad(self, grad_x):x = self.save_varsreturn jt.code(x.shape, x.dtype, [x, grad_x],cpu_src='''for (int i=0; i<in0_shape0; i++)@out(i) = @in1(i)*@in0(i)*4;''')

a = jt.random([10])
func = Func()
b = func(a)
print(b)
print(jt.grad(b,a))
实例2:使用stl和alias
下面的实例中,实现了一个简单的排序算法,演示了如何使用C++算法库中排序算法,以及使用别名alias来增加代码的可读性。
a = jt.array([3,2,1])
b = jt.code(a.shape, a.dtype, [a],
cpu_header="""
#include
@alias(a, in0)
@alias(b, out)
“”",
cpu_src="""
for (int i=0; i<a_shape0; i++)
@b(i) = @a(i);
std::sort(&@b(0), &@b(in0_shape0));
“”"
)
assert (b.data==[1,2,3]).all()
实例3:多输出的Code算子
在某些情况下,算子可能有多个输出,在这个实例中,演示了如何设置多输出。该算子输入为一维向量,输出为两个长度为1的向量,分别是最小值和最大值。
同之前实例不同的地方是,原来传入单个shape和dtype,这里传入的是一个shape数组和dtype数组。同时还在这个实例中演示了如何使用cout。
a = jt.array([3,2,1])
b,c = jt.code([(1,), (1,)], [a.dtype, a.dtype], [a],
cpu_header="""
#include
using namespace std;
“”",
cpu_src="""
@alias(a, in0)
@alias(b, out0)
@alias(c, out1)
@b(0) = @c(0) = @a(0);
for (int i=0; i<a_shape0; i++) {
@b(0) = std::min(@b(0), @a(i));
@c(0) = std::max(@c(0), @a(i));
}
cout << “min:” << @b(0) << " max:" << @c(0) << endl;
“”"
)
assert b.data == 1, b
assert c.data == 3, c
实例4:动态大小的输出
在某些情况下,算子的输出的大小可能是会变化的,比如把输入中大于0和小于等于0的数,分别紧密排列在两个向量中。下面的实例就实现了这样一个算子。
可以发现下面的数组的输出形状被设置成了负数,这是计图的特殊机制,传入负数代表这个数组的大小是不确定的,负数的绝对值,代表了这个维度最大上限。需要注意的是,动态大小只能在第一维度出现,而且在算法最后结束的时候,需要使用set_shape来设置确定的形状。
a = jt.array([5,-4,3,-2,1])

negtive shape for max size of vary dimension

b,c = jt.code([(-5,), (-5,)], [a.dtype, a.dtype], [a],
cpu_src="""
@alias(a, in0)
@alias(b, out0)
@alias(c, out1)
int num_b=0, num_c=0;
for (int i=0; i<a_shape0; i++) {
if (@a(i)>0)
@b(num_b++) = @a(i);
else
@c(num_c++) = @a(i);
}
b->set_shape({num_b});
c->set_shape({num_c});
“”"
)
assert (b.data == [5,3,1]).all()
assert (c.data == [-4,-2]).all()
综合实例5:使用Code算子实现三维点云K近邻查找
下面的实例展示了如何使用code算子,使用数行代码实现三维点云中十分常用的K近邻查找。Code算子的设计和实现,让用户既可以享受到Python语言的便捷与易用性,又可以获得高性能语言的性能。
可以留意到,在计图的Code算子中,可以使用openmp实现自动并行化的,关于openmp的使用,可以参考openmp文档。
a = jt.random((n,3))
b = jt.code([n, k], “int32”, [a],
cpu_header="#include “,
cpu_src=”""
using namespace std;
auto n=out_shape0, k=out_shape1;
// 使用openmp实现自动并行化
#pragma omp parallel for
for (int i=0; i<n; i++) {
// 存储k近邻的距离和下标
vector<pair<float,int>> id(n);
for (int j=0; j<n; j++) {
auto dx = @in0(i,0)-@in0(j,0);
auto dy = @in0(i,1)-@in0(j,1);
auto dz = @in0(i,2)-@in0(j,2);
id[j] = {dxdx+dydy+dz*dz, j};
}
// 使用c++算法库的nth_element排序
nth_element(id.begin(),
id.begin()+k, id.end());
// 将下标输出到计图的变量中
for (int j=0; j<k; j++)
@out(i,j) = id[j].second;
}"""
)
将计图使用code算子实现的K近邻查找,和PyTorch的算子用时进行比较,速度对比如下(k=10,点云数量n=[100,1000,10000]):
参数 n=100 n=1000 n=10000
PyTorch 433 µs 7.6 ms 623 ms
Jittor 68 µs 5.9 ms 484 ms
速度对比 6.4X 1.29X 1.29X
注:此处使用的K近邻算法为暴力算法,还存在更优的算法实现,由于文章篇幅有限,此处仅用于展示Code算子的使用。
实例6:使用CUDA进行加速
在这个实例中,使用CUDA实现了简单的两个2维向量相乘。并且反向传播对应的导数。
这个实例与之前的区别,定义了CUDA kernel,这需要用户有一定的CUDA基础。这里面的@ARGS_DEF,@ARGS分别是CUDA kernel函数的参数声明和参数传递,而@PRECALC包含了计图预处理内核的代码。除此之外,其他语法和CUDA保持高度一致。
import jittor as jt
from jittor import Function
jt.flags.use_cuda = 1

class Func(Function):
def execute(self, a, b):
self.save_vars = a, b
return jt.code(a.shape, a.dtype, [a,b],
cuda_src=’’’
global static void kernel1(@ARGS_DEF) {
@PRECALC
for (int i=blockIdx.x; i<in0_shape0; i+=gridDim.x)
for (int j=threadIdx.x; j<in0_shape1; j+=blockDim.x)
@out(i,j) = @in0(i,j)*@in1(i,j);
}
kernel1<<<32, 32>>>(@ARGS);
‘’’)

def grad(self, grad):a, b = self.save_varsreturn jt.code([a.shape, b.shape], [a.dtype, b.dtype], [a, b, grad],cuda_src='''__global__ static void kernel2(@ARGS_DEF) {@PRECALCfor (int i=blockIdx.x; i<in0_shape0; i+=gridDim.x)for (int j=threadIdx.x; j<in0_shape1; j+=blockDim.x) {@out0(i,j) = @in2(i,j)*@in1(i,j);@out1(i,j) = @in2(i,j)*@in0(i,j);}}kernel2<<<32, 32>>>(@ARGS);''')

a = jt.random((100,100))
b = jt.random((100,100))
func = Func()
c = func(a,b)
print©
print(jt.grad(c, [a, b]))
综合实例7:实现可以同时在GPU和CPU上运行的Pool算法
注:计图内部已经实现了Pool,用户不需要自己实现
import jittor as jt
from jittor import Function
jt.flags.use_cuda = 1

class Func(Function):
def execute(self, x):
out = jt.code([N,C,h,w], x.dtype, [x],
cuda_src=f’’’
global static void kernel1(@ARGS_DEF) {{
@PRECALC
int p3 = threadIdx.x;
int s3 = blockDim.x;
int p2 = threadIdx.y + blockIdx.x * blockDim.y;
int s2 = blockDim.y * gridDim.x;
int i1 = blockIdx.y;
int i0 = blockIdx.z;
for (int i3 = p3; i3 < out_shape3; i3 += s3)
for (int i2 = p2; i2 < out_shape2; i2 += s2) {{
int k3 = i3*{stride}-{padding};
int k2 = i2*{stride}-{padding};
int k3_ = min(k3 + {kernel_size}, in0_shape3);
int k2_ = min(k2 + {kernel_size}, in0_shape2);
k3 = max(0, k3);
k2 = max(0, k2);
@out(i0, i1, i2, i3) = @in0(i0, i1, k2, k3);
for (int p = k2; p < k2_; ++p)
for (int q = k3; q < k3_; ++q)
@out(i0, i1, i2, i3) = {op}(@out(i0, i1, i2, i3), @in0(i0, i1, p, q));
}}
}}
int tx = min(1024, out_shape3);
int ty = min(1024 / tx, out_shape2);
int bx = (out_shape2 - 1) / ty + 1;
int by = out_shape1;
int bz = out_shape0;
dim3 s1(bx, by, bz);
dim3 s2(tx, ty);
kernel1<<<s1, s2>>>(@ARGS);
‘’’,
cpu_src=f’’’
for (int i0=0; i0<out_shape0; i0++)
for (int i1=0; i1<out_shape1; i1++)
for (int i2=0; i2<out_shape2; i2++)
for (int i3=0; i3<out_shape3; i3++) {{
int k2 = i2*{stride}-{padding};
int k3 = i3*{stride}-{padding};
int k2_ = std::min(k2 + {kernel_size}, in0_shape2);
int k3_ = std::min(k3 + {kernel_size}, in0_shape3);
k2 = std::max(0, k2);
k3 = std::max(0, k3);
@out(i0, i1, i2, i3) = @in0(i0, i1, k2, k3);
for (int p = k2; p < k2_; ++p)
for (int q = k3; q < k3_; ++q)
@out(i0, i1, i2, i3) = std::{op}(@out(i0, i1, i2, i3), @in0(i0, i1, p, q));
}}
‘’’)
self.save_vars = x, out
return out

def grad(self, grad_x):x, pout = self.save_varsreturn jt.code(x.shape, x.dtype, [x, pout, grad_x],cuda_header=f'''@alias(pout, in1);''',cuda_src=f'''__global__ static void kernel3(@ARGS_DEF) {{@PRECALCint p3 = threadIdx.x;int s3 = blockDim.x;int p2 = threadIdx.y + blockIdx.x * blockDim.y;int s2 = blockDim.y * gridDim.x;int i1 = blockIdx.y;int i0 = blockIdx.z;for (int i3 = p3; i3 < pout_shape3; i3 += s3)for (int i2 = p2; i2 < pout_shape2; i2 += s2) {{int k3 = i3*{stride}-{padding};int k2 = i2*{stride}-{padding};int k3_ = min(k3 + {kernel_size}, in0_shape3);int k2_ = min(k2 + {kernel_size}, in0_shape2);k3 = max(0, k3);k2 = max(0, k2);int bo=1;for (int p = k2; p < k2_ && bo; ++p)for (int q = k3; q < k3_ && bo; ++q) {{if (@pout(i0,i1,i2,i3) == @in0(i0,i1,p,q)) {{atomicAdd(&@out(i0,i1,p,q), @in2(i0,i1,i2,i3));bo=0;}}}}}}}}cudaMemsetAsync(out_p, 0, out->size);int tx = min(1024, pout_shape3);int ty = min(1024 / tx, pout_shape2);int bx = (pout_shape2 - 1) / ty + 1;int by = pout_shape1;int bz = pout_shape0;dim3 s1_(bx, by, bz);dim3 s2_(tx, ty);kernel3<<<s1_, s2_>>>(@ARGS);''',cpu_src=f'''@alias(pout, in1);for (int i=0; i<out_shape0; i++)for (int j=0; j<out_shape1; j++)for (int k=0; k<out_shape2; k++)for (int l=0; l<out_shape3; l++) @out(i,j,k,l) = 0;for (int i0=0; i0<pout_shape0; i0++)for (int i1=0; i1<pout_shape1; i1++)for (int i2=0; i2<pout_shape2; i2++) for (int i3=0; i3<pout_shape3; i3++) {{int k3 = i3*{stride}-{padding};int k2 = i2*{stride}-{padding};int k3_ = std::min(k3 + {kernel_size}, in0_shape3);int k2_ = std::min(k2 + {kernel_size}, in0_shape2);k3 = std::max(0, k3);k2 = std::max(0, k2);int bo=1;for (int p = k2; p < k2_ && bo; ++p)for (int q = k3; q < k3_ && bo; ++q) {{if (@pout(i0,i1,i2,i3) == @in0(i0,i1,p,q)) {{@out(i0,i1,p,q) += @in2(i0,i1,i2,i3);bo=0;}}}}}}''')

N,C,H,W = [2,10,100,100]
stride = 2
padding = 0
kernel_size = 3
op = “max”

x = jt.random((N,C,H,W))
h = (H+padding2-kernel_size)//stride+1
w = (W+padding
2-kernel_size)//stride+1

func = Func()
out = func(x)
print(out)
print(jt.grad(out, x))

自定义算子高性能开发相关推荐

  1. 10大高性能开发宝石,我要消灭一半程序员!

    程序员经常要面临的一个问题就是:如何提高程序性能? 这篇文章,我们循序渐进,从内存.磁盘I/O.网络I/O.CPU.缓存.架构.算法等多层次递进,串联起高性能开发十大必须掌握的核心技术. - I/O优 ...

  2. 高性能开发,别点,发际线要紧!

    作者:轩辕之风O 来源:编程技术宇宙 -前言- 程序员经常要面临的一个问题就是:如何提高程序性能? 这篇文章,我们循序渐进,从内存.磁盘I/O.网络I/O.CPU.缓存.架构.算法等多层次递进,串联起 ...

  3. 学会这10大高性能开发技术,轻松躲过裁员名单!

    来源 | 编程技术宇宙 责编 | Carol 封图 | CSDN 下载自视觉中国 程序员经常要面临的一个问题就是:如何提高程序性能? 这篇文章,我们循序渐进,从内存.磁盘I/O.网络I/O.CPU.缓 ...

  4. 高性能开发十大必须掌握的核心技术

    作者 | 轩辕之风O 来源 | 编程技术宇宙(ID: xuanyuancoding) 程序员经常要面临的一个问题就是:如何提高程序性能? 这篇文章,我们循序渐进,从内存.磁盘I/O.网络I/O.CPU ...

  5. 图文详解!10大高性能开发核心技术+

    程序员经常要面临的一个问题就是:如何提高程序性能? 这篇文章,我们循序渐进,从内存.磁盘I/O.网络I/O.CPU.缓存.架构.算法等多层次递进,串联起高性能开发十大必须掌握的核心技术. - I/O优 ...

  6. 小孢子:在?我用本地环境pytest带你玩自定义算子

    1 多玩法Python调试框架pytest 初学入门 大家好python通用测试框架的是unittest+HTMLTestRunner,这段时间看到了pytest文档,发现这个框架和丰富的plugin ...

  7. 高性能开发十大核心技术

    高性能开发十大核心技术 目录 高性能开发十大核心技术 I/O优化:零拷贝技术 I/O优化:多路复用技术 select有三弊,epoll有三优. 系统优化:线程池技术 系统优化:无锁编程技术 网络优化: ...

  8. 【VM服务管家】VM4.x算子SDK开发_3.4 控件嵌入类

    目录 3.4.1 图片存储:图片保存的方法 3.4.2 辅助十字线:给图像添加辅助十字线的方法 3.4.3 控件调用:在WPF中使用Winform控件的方法 3.4.4 图形改变事件:渲染控件上图形改 ...

  9. 多图详解!10大高性能开发核心技术

    程序员经常要面临的一个问题就是:如何提高程序性能? 这篇文章,我们循序渐进,从内存.磁盘I/O.网络I/O.CPU.缓存.架构.算法等多层次递进,串联起高性能开发十大必须掌握的核心技术. - I/O优 ...

最新文章

  1. php和python写爬虫-一个简单的Python写的XML爬虫
  2. spring junit 测试
  3. 【错误记录】Groovy工程中的文件查找策略 ( main 函数中需要使用 src/main/groovy/Script.groovy | Groovy 脚本直接使用代码相对路径 )
  4. 关于微机开操作票的研究22437
  5. __attribute__((packed))
  6. cmakelist .so_卡罗拉汽车音响改装SO匠心三分频,黄金声学
  7. 被黑客盯上了…数据都给打包带走了…
  8. 开源自研新基建,内核实践云数据,当此方为嘉年华 - 2020嘉年华大会侧记
  9. 戒指的戴法,终于收齐了!
  10. uniapp之app自动更新
  11. 要求公开华人程序员自杀真相,清华学霸被Facebook开除了
  12. 树莓派无线网络及VNC设置
  13. 计算机网络之无线局域网
  14. AndroidStudio - - - 点击头像更换头像_菜单选择_相机拍照与相册获取
  15. quartz-深度解析
  16. countif函数比较两列不同_《excel中用if函数比对两表格数据》 用Excel函数对比两列数的相同与不同...
  17. 证明:旋转矩阵是正交矩阵
  18. 结构建模设计——Solidworks软件之装配体操作基本总结二(装配体内编辑零件、新建零件、标准配合操作)
  19. python布尔函数_Python内置bool函数详细介绍
  20. 诺基亚n1系统更新显示无网络_国行版诺基亚8 Sirocco推送安卓9.0系统更新

热门文章

  1. 阿里云如何将服务器添加至跳板机,如何将服务器添加至jumpsever
  2. 简单介绍互联网领域选择与营销方法
  3. SpringBoot整合Shiro安全框架完整实现
  4. NNVM Compiler,AI框架的开放式编译器
  5. RGB Color Codes Chart
  6. TensorRT-优化-原理
  7. 目标形体形状轮廓重建:ICCV2019论文解析
  8. 2021年大数据Spark(二十八):SparkSQL案例三电影评分数据分析
  9. SyntaxError: Non-ASCII character ‘\xe4‘ in file xx(路径)
  10. Java 实例化的理解