MindSpore自定义算子中的张量维度问题
技术背景
在前面的几篇博客中,我们介绍了MindSpore框架下使用CUDA来定义本地算子的基本方法,以及配合反向传播函数的使用,这里主要探讨一下MindSpore框架对于CUDA本地算子的输入输出的规范化形式。
测试思路
MindSpore使用的CUDA算子规范化接口形式为:
extern "C" int CustomOps(int nparam, void **params, int *ndims, int64_t **shapes, const char **dtypes,
void *stream, void *extra)
也就是说,我们在一个.cu文件中按照这种形式写好函数接口,其中主要是规范化输入输出的形式,然后再将各项输入传给写好的CUDA Kernel函数进行计算并获得返回值。
我们可以使用一个Kernel打印函数的测试案例来说明MindSpore对于输入输出的处理:
#include <iostream>
#define THREADS 1024
__global__ void OpsKernel(const int shape0, const int *input){
auto i = blockIdx.x * THREADS + threadIdx.x;
if (i < shape0){
printf("%d\n", input[i]);
}
}
在这个函数体内,会把指定大小范围内的input的内容打印出来。
常数输入
首先我们来看一下最简单的常数输入,可以用一个最简单的整数来测试,对应的CUDA算子代码为:
// nvcc --shared -Xcompiler -fPIC -o test_shape.so test_shape.cu
#include <iostream>
#define THREADS 1024
__global__ void OpsKernel(const int shape0, const int *input){
auto i = blockIdx.x * THREADS + threadIdx.x;
if (i < shape0){
printf("%d\n", input[i]);
}
}
extern "C" int CustomOps(int nparam, void **params, int *ndims, int64_t **shapes, const char **dtypes,
void *stream, void *extra){
int *input = static_cast<int*>(params[0]);
OpsKernel<<<1, THREADS>>>(shapes[0][0], input);
return 0;
}
调用CUDA算子的Python代码为:
import os
import numpy as np
import mindspore as ms
from mindspore import ops, Tensor, context
context.set_context(mode=context.GRAPH_MODE, device_target="GPU")
CURRENT_PATH = os.path.abspath(__file__)
CustomOps = ops.Custom(CURRENT_PATH.replace(".py", ".so:CustomOps"),
out_shape=lambda x:x,
out_dtype=ms.int32,
func_type="aot")
T0 = Tensor([7], ms.int32)
print (T0)
CustomOps(T0)
运行的指令为:
$ nvcc --shared -Xcompiler -fPIC -o test_shape.so test_shape.cu && python3 test_shape.py
[7]
7
需要注意的是,这里只能给MindSpore内置的几种Tensor变量,如果是直接调用CustomOps(7)会报一个段错误。
高维张量输入
这里一维的张量输入我们就不做讨论了,因为跟前面用到的常数输入本质上是一样的形式。这里我们用一个二维的张量来做一个测试,CUDA代码保持不动,只修改Python代码中的输入:
import os
import numpy as np
import mindspore as ms
from mindspore import ops, Tensor, context
context.set_context(mode=context.GRAPH_MODE, device_target="GPU")
CURRENT_PATH = os.path.abspath(__file__)
CustomOps = ops.Custom(CURRENT_PATH.replace(".py", ".so:CustomOps"),
out_shape=lambda x:x,
out_dtype=ms.int32,
func_type="aot")
T0 = Tensor(np.arange(12).reshape((4, 3)), ms.int32)
print (T0)
CustomOps(T0)
运行结果为:
$ nvcc --shared -Xcompiler -fPIC -o test_shape.so test_shape.cu && python3 test_shape.py
[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]]
0
1
2
3
需要注意的是,我们在CUDA的打印函数中设置的打印输出大小是输入张量的第一个维度的大小,我们给的是一个(4,3)大小的张量,因此会顺序打印4个数出来。这里我们也能够发现MindSpore在进行输入的规范化的时候,会自动压平输入的张量变成一个维度。因此这里的调用代码等价于先对输入张量做一个reshape,然后再把第一个维度对应大小的张量元素打印出来。如果要打印所有的元素也很简单,可以修改一下CUDA代码:
// nvcc --shared -Xcompiler -fPIC -o test_shape.so test_shape.cu
#include <iostream>
#define THREADS 1024
__global__ void OpsKernel(const int shape0, const int *input){
auto i = blockIdx.x * THREADS + threadIdx.x;
if (i < shape0){
printf("%d\n", input[i]);
}
}
extern "C" int CustomOps(int nparam, void **params, int *ndims, int64_t **shapes, const char **dtypes,
void *stream, void *extra){
int *input = static_cast<int*>(params[0]);
int elements = 1;
for (int i=0; i<ndims[0]; i++){
elements *= shapes[0][i];
}
OpsKernel<<<1, THREADS>>>(elements, input);
return 0;
}
通过定义一个elements变量用于存储对应张量的元素数量,然后再逐一打印出来即可,执行结果为:
$ nvcc --shared -Xcompiler -fPIC -o test_shape.so test_shape.cu && python3 test_shape.py
[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]]
0
1
2
3
4
5
6
7
8
9
10
11
输出规范化
当我们使用ops.Custom算子时,如果指定了out_dtype和out_shape,那么算子会自动帮我们分配好相应的device memory空间。那么我们在CUDA计算的时候可以直接修改对应的内存空间:
// nvcc --shared -Xcompiler -fPIC -o test_shape.so test_shape.cu
#include <iostream>
#define THREADS 1024
__global__ void OpsKernel(const int shape0, const int *input, float *output){
auto i = blockIdx.x * THREADS + threadIdx.x;
if (i < shape0){
output[i] = input[i] * 0.5;
}
}
extern "C" int CustomOps(int nparam, void **params, int *ndims, int64_t **shapes, const char **dtypes,
void *stream, void *extra){
int *input = static_cast<int*>(params[0]);
float *output = static_cast<float*>(params[1]);
int elements = 1;
for (int i=0; i<ndims[0]; i++){
elements *= shapes[0][i];
}
OpsKernel<<<1, THREADS>>>(elements, input, output);
return 0;
}
这里我们对算子的功能做了一点调整,我们输出的结果是整个张量的元素值乘以0.5,同时也把一个整形变量转化成了一个浮点型变量。其运行Python代码也要做一点调整:
import os
import numpy as np
import mindspore as ms
from mindspore import ops, Tensor, context
context.set_context(mode=context.GRAPH_MODE, device_target="GPU")
CURRENT_PATH = os.path.abspath(__file__)
CustomOps = ops.Custom(CURRENT_PATH.replace(".py", ".so:CustomOps"),
out_shape=lambda x:x,
out_dtype=ms.float32,
func_type="aot")
T0 = Tensor(np.arange(12).reshape((4, 3)), ms.int32)
print (T0)
output = CustomOps(T0)
print (output)
这里主要是修改了out_dtype为浮点型,这里如果写错了,会直接导致内存溢出。上述代码的运行结果为:
$ nvcc --shared -Xcompiler -fPIC -o test_shape.so test_shape.cu && python3 test_shape.py
[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]]
[[0. 0.5 1. ]
[1.5 2. 2.5]
[3. 3.5 4. ]
[4.5 5. 5.5]]
可以看到这里输出的张量形状是跟输入保持一致的,即时这个输入张量在经过MindSpore的Custom算子接口时已经被压平成一个一维张量,但是因为我们设置了out_shape=lambda x:x,这表示输出的张量shape跟输入的张量shape一致,当然,直接用Python的列表来给out_shape赋值也是可以的。例如我们写一个输入输出不同shape的案例:
// nvcc --shared -Xcompiler -fPIC -o test_shape.so test_shape.cu
#include <iostream>
#define THREADS 1024
__global__ void OpsKernel(const int shape0, const int *input, int *output){
auto i = blockIdx.x * THREADS + threadIdx.x;
if (i < shape0){
atomicAdd(&output[0], input[i]);
}
}
extern "C" int CustomOps(int nparam, void **params, int *ndims, int64_t **shapes, const char **dtypes,
void *stream, void *extra){
int *input = static_cast<int*>(params[0]);
int *output = static_cast<int*>(params[1]);
int elements = 1;
for (int i=0; i<ndims[0]; i++){
elements *= shapes[0][i];
}
OpsKernel<<<1, THREADS>>>(elements, input, output);
return 0;
}
这个Kernel函数的主要功能是通过一个atomicAdd函数,把输入张量的所有元素做一个求和,这样输出的张量的shape只有[1],对应的Python调用形式也要做一定的调整:
import os
import numpy as np
import mindspore as ms
from mindspore import ops, Tensor, context
context.set_context(mode=context.GRAPH_MODE, device_target="GPU")
CURRENT_PATH = os.path.abspath(__file__)
CustomOps = ops.Custom(CURRENT_PATH.replace(".py", ".so:CustomOps"),
out_shape=[1],
out_dtype=ms.int32,
func_type="aot")
T0 = Tensor(np.arange(12).reshape((4, 3)), ms.int32)
print (T0)
output = CustomOps(T0)
print (output)
由于atomicAdd(addr, element)原子操作要求输入输出的类型要一致,因此这里我们还是使用的int类型的output,输出结果如下所示:
$ nvcc --shared -Xcompiler -fPIC -o test_shape.so test_shape.cu && python3 test_shape.py
[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]]
[66]
总结概要
当我们使用GPU进行快速运算时,虽然可以用成熟的深度学习框架如MindSpore和PyTorch等进行实现,但其实从速度上来说,最快不过直接使用C/C++的CUDA来实现。也正是因为如此,在MindSpore框架中支持了对CUDA实现的算子的直接调用,只是在格式规范上有一定的要求。本文主要介绍MindSpore调用本地CUDA算子的一些规范化和技巧。
版权声明
本文首发链接为:https://www.cnblogs.com/dechinphy/p/custom-ops-shape.html
作者ID:DechinPhy
更多原著文章:https://www.cnblogs.com/dechinphy/
请博主喝咖啡:https://www.cnblogs.com/dechinphy/gallery/image/379634.html
MindSpore自定义算子中的张量维度问题的更多相关文章
- MindSpore自定义模型损失函数
技术背景 损失函数是机器学习中直接决定训练结果好坏的一个模块,该函数用于定义计算出来的结果或者是神经网络给出的推测结论与正确结果的偏差程度,偏差的越多,就表明对应的参数越差.而损失函数的另一个重要性在 ...
- TensorFlow 中的张量,图,会话
tensor的含义是张量,张量是什么,听起来很高深的样子,其实我们对于张量一点都不陌生,因为像标量,向量,矩阵这些都可以被认为是特殊的张量.如下图所示: 在TensorFlow中,tensor实际上就 ...
- iOS 如何自定义UISearchBar 中textField的高度
iOS 如何自定义UISearchBar 中textField的高度 只需设置下边的方法就可以 [_searchBar setSearchFieldBackgroundImage:[UIImage i ...
- 如何得到自定义UITableViewCell中的按钮所在的cell的indexPath.row
在自定义UITableViewCell中创建了一个按钮. 想在点击该按钮时知道该按钮所在的cell在TableView中的行数.就是cell的 indexPath.row两种方法都很好.-(IBAct ...
- Xcode自定义Eclipse中常用的快捷键
转载自http://joeyio.com/2013/07/22/xcode_key_binding_like_eclipse/ Xcode自定义Eclipse中常用的快捷键 22 July 2013 ...
- BI之SSAS完整实战教程7 -- 设计维度、细化维度中 :浏览维度,细化维度
上篇文章我们已经将Dim Geography维度设计好. 若要查看维度的成员, AS需要接收该维度的详细信息(包括已创建的特性.成员属性以及多级层次结构), 通过XMLA与AS的实例进行通信. 今天我 ...
- 如何自定义UIPickerView中文本的大小和文本靠左或靠右显示?
需要重写UIPickerView中的 -(UIView*)pickerView:(UIPickerView*)pickerView viewForRow:(NSInteger)row forCompo ...
- 关于在App_Code文件夹自定义类中Session无法使用
由于前台页面需要调用App_Code中自定义类的函数,但在自定义类中找不到Session,解决方法如下: 新建一个类session,并自己定义函数GetSession(),引用命名空间 System. ...
- Dotfuscator自定义规则中的元素选择
Dotfuscator是专业的.NET程序代码保护软件.是支持规则自定义的,你可以对重命名.程序控制流.字符串加密等等功能自定义规则.在进行规则自定义过程中,可以通过元素的不同选择,满足自己的程序需要 ...
- AngularJs中,如何在父元素中调用子元素为自定义Directive中定义的函数?
最近一段时间准备使用AngularJs中的自定义Directive重构一下代码. 在这里说明一下,把自定义控件封装成Directive并不一定是要复用,而是要让代码结构更加清晰.就好像你将一个长方法拆 ...
随机推荐
- 谁更适合搭配甜点显卡?i7-13700KF、锐龙7 7800X3D对比:游戏相当 生产力Intel强了50%
一.前言:如果搭配2000元甜点显卡 i7-13700KF和锐龙7 7800X3D谁更有性价比? 现在AMD最受欢迎的处理器无疑是拥有96MB三级缓存的锐龙7 7800X3D,这是一颗专为游戏而生的处 ...
- React 的学习笔记一 (未完结)
一.React 是什么 React 是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 库.使用 React 可以将一些简短.独立的代码片段组合成复杂的 UI 界面,这些代码片段被称作 ...
- 优化算法之梯度下降|Matlab实现梯度下降算法
题目要求: 使用Matab实现梯度下降法 对于函数: min f ( x ) = 2 x 1 2 + 4 x 2 2 − 6 x 1 − 2 x 1 x 2 \min f(x)=2 x_{1}^{ ...
- public private protected 的辨析
一. public 1.作为类内成员的访问修饰符时,由public修饰的成员数据或者成员函数可以在类外(即派生类内以及实例化的对象后)以及类内进行随意访问 可以看到public成员Data在类外是可访 ...
- 小知识:Linux如何删除大量小文件
环境:RHEL 6.5 + Oracle 11.2.0.4 需求:使用df -i巡检发现Inodes使用率过高,需要清理删除文件来解决.如果Inodes满,该目录将不能写,即使df -h查看还有剩余空 ...
- delphi中 注意一点,record 类型 参数默认是 值拷贝,class 参数 默认是传地址;值传递,指针传递、引用传递
作为函数的入参,若是record类型,默认是值拷贝,效率低,若要传指针,需要加 var ; 作为函数的入参,若是 class类型,默认是传地址,不需要加var unit Unit1; interfac ...
- .NET 云原生架构师训练营(模块二 基础巩固 RabbitMQ 业务场景详解)--学习笔记
2.6.5 RabbitMQ -- 业务场景详解 异步处理 应用解耦 流量削锋 日志处理 异步处理 串行方式 并行方式 异步方式 串行方式 _userRepo.Add(user); _emailSer ...
- 点亮.NET的文字云艺术之光——Sdcb.WordCloud 2.0
点亮.NET的文字云艺术之光--Sdcb.WordCloud 2.0 作为一名.NET开发者,你是否渴望拥有一个强大且易用的库,用以在你的应用程序中创造美轮美奂的文字云?我在经过一轮农历新年前的码力全 ...
- Power BI 10 DAY
Power BI 上下文 筛选上下文只管筛选,行上下文只管迭代,两者互不干涉,各司其职 计算列操作会自动创建行上下文 度量值不会自动创建行上下文,需要人为使用迭代函数进行行上下文的创建 (迭代函数本身 ...
- Typora 使用和自定义设置
版本 新的版本都已经收费, 因此继续使用原来的beta版本, 当前使用的是0.9.92 修改字体 默认的字体偏大 File -> Preference -> Appearance, Ope ...