开源之夏-MindSpore CPU算子全流程开发指导

举报
Super_WZB 发表于 2022/09/29 10:59:01 2022/09/29
【摘要】 此文档详细介绍了 MindSpore CPU 算子开发流程,主要帮助大家了解 CPU 算子开发需要写什么,各种文件的作用以及应该怎么写这些文件

博客目录如下:

一、项目概述

1. 开源之夏活动介绍

2. 昇思MindSpore介绍

3. CPU正向算子开发介绍

二、ParallelConcat算子介绍

1. MindSpore ParallelConcat算子介绍

2. TensorFlow ParallelConcat算子案例

3. MindSpore相似Concat算子案例

三、CPU算子开发流程

1. 算子开发文件

2. 算子python侧前端接口开发

3. 算子c++侧前端接口开发

4. 算子cpu_kernel底层核函数开发

5. 交付件准备

四、参考文献


一、 项目概述

1. 开源之夏活动介绍

开源之夏(英文简称 OSPP)是开源软件供应链点亮计划的系列暑期活动,它由中国科学院软件研究所与openEuler社区共同举办,旨在鼓励在校学生积极参与开源软件的开发维护,促进优秀开源软件社区的蓬勃发展,培养和发掘更多优秀的开发者。活动联合国内外各大开源社区,针对重要开源软件的开发与维护提供项目任务,并面向全球高校学生开放报名

2. 昇思Mindspore介绍

MindSpore是一个全场景深度学习框架,旨在实现易开发、高效执行、全场景覆盖三大目标,其中易开发表现为API友好、调试难度低,高效执行包括计算效率、数据预处理效率和分布式训练效率,全场景则指框架同时支持云、边缘以及端侧场景Mindspore欢迎每一个开发者都参与的社区里,为全场景AI框架MindSpore添砖加瓦!具体了解可以点击一下链接进行跳转:

Mindspore 官网 :可以全方位了解MindSpore,包括安装、教程、文档、社区、资源下载和资讯栏目等。

MindSpore 代码

MindSpore Giteefork仓库后即可随时跟踪MindSpore最新进展,参与issues讨论提交代码

MindSpore Github GiteeMindSpore代码镜像,习惯用github的开发者可以在这里进行MindSpore的学习

MindSpore 论坛在论坛中与其他开发者交流,共同学习,共同成长

针对本次开源之夏活动,昇思MindSpore社区共设置了21个项目(包含1Kube-Edge社区集成项目),难度包含基础与进阶,适合不同层次的同学参与。每个任务均有专业导师提供1v1专业指导,开发者可以登录项目页了解详细信息,也可以直接联系项目导师进行咨询。

3. CPU正向算子开发介绍

算子是MindSpore深度学习框架中的一个基本组件,例如常见的卷积操作、激活函数、全连接、批归一化等,示例可见官网MindSpore框架中的算子包括算子前端和算子后端,算子前端是指提供用户使用的低阶算子Python API,后端指对用户不可见的算子底层实现逻辑。MindSpore支持CPUGPUAscend三种硬件设备。其中,Ascend是指昇腾AI处理器,根据设备差异有多个不同的算子后端实现,运行在Ascend上的包括AI CPU算子和AI Core算子。

CPU算子开发的目的,就是要在MindSpore框架中通过调用算子API来使用运行在CPU硬件平台上的相应算子。

(1) CPU正向算子开发的整体流程如下:

(2) CPU正向算子开发涉及文件如下:

(3) CPU正向算子开发目标:

MindSpore CPU算子开发的基本目标是与友商竞品完成对齐,包括功能、规格、精度和性能等方面。如果前端用户资料不足以明确算子功能或其他相关信息,可参考PyTorchTensorFlow的算子实现,具体资料和源码链接如下:

PyTorch官方文档:https://pytorch.org/docs/stable/index.html

PyTorch中文文档:https://pytorch-cn.readthedocs.io/zh/latest

PyTorch官方源码:https://github.com/pytorch/pytorch

TensorFlow官方文档:https://tensorflow.google.cn/install

TensorFlow中文文档:https://tensorflow.google.cn/

TensorFlow官方源码:https://github.com/tensorflow


二、 ParallelConcat算子介绍

1. MindSpore ParallelConcat算子介绍

(1) 算子功能

该算子的功能为沿着第一个维度上拼接包含N张量的列表所有张量的第一维度必须为1ParallelConcat算子Concat算子的功能非常类似,而ParallelConcatConcat之间的区别就在于,Concat要求在操作开始之前计算所有输入,但不要求在构图期间知道各个输入TensorshapeParallelConcat算子会在输入Tensor可用时将其片段先行复制到输出中,在某些情况下这样可以提供性能优势。

该算子的前端接口如下:

昇思 Mindspore ParallelConcat 算子 接口 文档

(2) 算子输入与输出

上图为ParallelConcat算子的输入与输出信息,从官方文档可知算子的输入和输出存在以下要求:

输入 "values" 为一个元组tuple或者列表list

② 输入 "values" 的长度必须大于等于1,不能为空。

③  "values" 中的元素均为Tensor类型并且必须具有相同的数据类型shape

④  "values" 中的所有Tensor的第一个维度必须为1

⑤ 输出 "y" 也是一个Tensor并且与输入 "values" 具有相同的数据类型。

输出 "y" 第一个维度取决于输入"values" 的长度。

⑦ 输出 "y" 的其他维度与输入 "values" 中任Tensor的其他维度相同。

(3) 算子实现逻辑

在清楚了算子的功能和对于输入输出的限制后,可以简要概括算子的实现逻辑如下:

① Python侧前端接口获取输入 "values"

② C++侧接口的InferType函数中对输入 "values" 类型和数据类型进行校验如果输入 "values" 不全为Tensor或存在数据类型不完全一致的情况则抛出异常若无异常则推导出输出 "y" 具有和输入 "values" 一样的数据类型

③ C++侧接口的InferShape函数中对于输入 "values" shape进行校验首先获取输入 "values" 的长度N以及其中第一个元素(Tensor[0])的shape并判断其第一个维度是否为1再遍历输入 "values" 中剩余的所有Tensor12N-1)的shape,将其与Tensor[0]shape进行对比,如果存在TensorshapeTensor[0]shape不相同的情况则抛出异常;如果无异常则将输入 "values" 的长度N作为输出 "y" shape的第一个维度的大小N,)同时将Tensor0shape除去第一维后的剩余维度(,*)作为输出 "y" 的后续维度。最后判断当前算子是否处于动态shape推导阶段,如果是则需要获取获取输入 "values" 的最大shape和最小shape,并且同时返回推导出的输出 "y" shape (N*)和最大最小shape;如果不是则直接返回输出 "y" shape(N*…)

④ C++侧的cpu_kernel底层函数中获取输出 "y" 的内存地址out_addr和输入 "values" 中每个Tensor的内存地址列表input_addr_list,之后遍历每一个输入"values"中的Tensor,并将其数值复制到out_addr中,最后返回输出 "y" 的内存地址。此时输出 "y" 为一个Tensor,其dtype为输入 "vlaues" 中任Tensordtype,其shape第一维度为 "values" 的长度,剩余维度与 "values" 中任Tensor的其他维度相同,数值为输入 "values" 中所有Tensor在第一维度拼接后的结果。

最终M indSpore ParallelConcat 算子逻辑流程图 如下(见下一页)

(4) 算子异常处理

① 输入 "values" 不是一个Tensortuplelist,返回

" TypeError: For ParallelConcat, the input "values" must be a list or tuple of Tensors. But got: xxx."

② 输入 "values" Tensor数据类型不属于[float16, float32, float64, int16, int32]

返回

" TypeError: For ParallelConcat, the type of the tensor in input "values" must be in

[float16, float32, float64, int16, int32], but got xxx."

③ 输入 "values" 中所有Tensor数据类型完全相同,返回

" TypeError: For ParallelConcat, the type of the Tensor in input "values" must be the same, but got Tensor[i] with type xxx and Tensor[j] with type yyy."

④ 输入 "values" 的长度小于1,返回

" ValueError: For ParallelConcat, the length of input "values" must be greater or equal to 1, but got length xxx."

⑤ 输入 "values" 中任Tensor第一个维度不为1,返回

" ValueError: For ParallelConcat, the first dimension of all Tensors in the input "values" must be 1, but got Tensor[i]‘s first dimension x."

⑥ 输入 "values" 中所有Tensorshape不完全相同,返回

" ValueError: For ParallelConcat, the shape of the Tensor in input "values" must be the same, but got Tensor[i] with shape xxx and Tensor[j] with shape yyy."

MindSpore ParallelConcat算子逻辑流程图


2. TensorFlow友商ParallelConcat算子案例

(1) 算子功能

沿第一维连接 N 张量的列表。接口文档如下:

(2) 算子输入与输出

可以看出TensorFlow的输入和输出均与MindSpore一致,额外的输入Shape实际上也不是必须的参数。

(3) 算子源码实现

由于算子源码包含多个文件,并且代码量较大,故此处只列举核心代码,其余代码可参考TensorFlow 官方代码仓库

算子前端Python接口定义文件:

依据TensorFlow官网的ParallelConcat接口文档查找arrary_ops.py文件未找到ParallelConcat前端接口,但是在Stack算子的接口定义中调用了parallel_concat算子,而其所属算子文件已更改,并且查阅相关资料后得知gen_array_ops文件是在编译时生成的文件,因此此处不过多介绍

② 算子C++侧输出推导文件

输入输出设置:

InferType

判断 "values" 中所有Tensordtype是否相同

判断 "values" 中所有Tensordtype是否为支持的数据类型

InferShape

判断输入 "values" 的长度是否大于等于1

判断输入Tensor的第一个维度是否为1

判断输入 "values" 中所有TensorShape是否相同

③ 算子底层实现文件

④ 算子反向实现文件

经查阅文档和源码,ParallelConcat算子无反向

⑤ 算子Python接口单元测试文件

3. MindSpore相似Concat算子案例

(1) 算子功能

在指定轴上拼接输入Tensor输入的是一个tuple其元素相同,即。将给定的轴设为,并且输入元素的数量设为。对于第数据,shape是第元素的第维度。则输出tensorshape为:


接口文档如下:

(2) 算子输入与输出

从表中可以看出Concat支持所有的数据类型,同时比ParallelConcat算子多了一个输入属性 "axis" 其数值用于指定Tensor进行拼接的维度。由此我们可以看出,实际上ParallelConcat算子就是Concat算子在axis=0,输入 "values" 中所有Tensor 的第一个维度均为1且输入数据dtype=[int16,int32,float16,float32,float64]情况下的特例,因此我们完全可以参照Concat算子的源码进行开发,并且可以做一定的简化。这也就是为什么Issue中说ParallelConcat在某些情况下可以提供性能优势的原因。

(3) 算子源码实现

由于算子源码包含多个文件,并且代码量较大,故此处只列举核心代码,其余代码可参考Concat 已合入代码 PR

算子前端Python接口定义文件:

② 算子C++侧输出推导文件

InferType

判断 "values" 是否为一个tuplelist

判断 "values" 中所有Tensordtype是否相同

判断 "values" 中所有Tensordtype是否为支持的数据类型

InferShape

判断输入 "values" 的长度是否大于等于1

判断输入Tensor的第一个维度是否为1

Concat算子对于输入Tensor的第一个维度无要求,因此ParallelConcat算子需要添加这一检验。

判断输入 "values" 中所有TensorShape是否相同

Concat算子对于输入Tensor的第一个维度无要求,因此ParallelConcat算子需要添加这一检验。

算子底层实现文件

④ 算子反向实现文件

经查阅文档和源码,ParallelConcat算子无反向

⑤ 算子Python接口单元测试文件

未查询到Concat算子的接口单元测试文件,因此ParallelConcat算子需要添加这一文件。

三、 算子开发流程

本章会先对本次ParallelConcat算子需要开发的文件进行一个简要介绍,然后参照Concat算子,以伪代码的形式详细阐述ParallelConcat算子各个文件的开发思路和实现逻辑。

1. 算子开发文件

本次ParallelConcat算子需要开发的文件以及内容简介如下:

(1) 算子前端Python接口定义文件

Python侧算子原语都定义在MindSpore仓的mindspore/python/mindspore/ops/operations/文件夹中,根据算子的不同功能分类定义在不同的python文件中。具体而言ParallelConcat算子属于array ops,因此需要定义在array_ops.py文件中注册完算子,需要在 mindspore/pyth on/mindspore/ops/operations/__init__.py 中对应的算子类别中添加算子名。

① mindspore/ops/operations/array_ops.py文件

定义算子名

编写初始化_init_方法

② mindspore/ops/operations/_init_.py文件

导入算子接口

(2) 算子C++侧输出推导文件

每个算子有各自的特定功能,算子对输入的shapetype可能有特定的要求,输入数据通过算子计算以后也可能会使shapedype改变。因此,我们需要了解算子的功能和大致计算逻辑,对 每个算子输入进行合理校验,以及对输出的shapetype给出推理结果。

① mindspore/core/ops/parallel_concat.h文件

声明算子类与接口函数

定义字符串常量表达式,内容为算子名称

定义类的构造函数,添加输入输出的Name

定义的析构函数

宏定义MIND_API_BASE_MEMBER ()

定义算子推理函数。

② mindspore/core/ops/parallel_concat.cc文件

实现类外算子推理函数

实现InferShape()函数,对输入的Shape进行校验并推导输出Shape

实现InferType()函数,对输入的Type进行校验并推导输出Type

宏定义REGISTER_PRIMITIVE_EVAL_IMPL用于注册算子推理函数

③ mindspore/core/base/core_ops.h文件

定义PrimitivePtr

(3) 算子底层实现文件

① mindspore/ccsrc/plugin/device/cpu/parallel_concat_cpu_kernel.h文件

注册算子CPU算子类继承于NativeCpuKernelMod,并创建在mindspore/kernel 命名空间下

定义构造函数和析构函数

声明InitKernelLaunch函数

使用override 定义类保护函数GetOpSupport,用于框架获取可支持的数据类型

② mindspore/ccsrc/plugin/device/cpu/parallel_concat_cpu_kernel.cc文件

算子实现算子具体实现类是继承自 NativeCpuKernelMod 类的子类需根据算子实际情况,重写 InitKernel ()、和 Launch()等方法,主要功能如下:

实现InitKernel ()函数,进行算子输入输出 shape 和属性值的获取、format 转换,并根据 shape 信息,计算输入输出以及 workspace 的内存需求。

实现Launch()函数,完成算子核心功能的调用执行。

实现LaunchKernel()模板函数实现算子的计算逻辑。

实现GetOpSupport()函数,注册算子所支持的数据类型,对于各种可变类型组合,需要进行补齐,详细方法可以参考当前库里源码。

MS_KERNEL_FACTORY_REG注册宏将算子类注册到工厂中。

(4) 算子反向实现文件

经查阅文档和源码,ParallelConcat算子无反向

(5) 算子Python接口单元测试文件

① tests/ut/python/ops/test_ops.py文件

新增算子在tests/ut/python/ops/test_ops.py中添加算子ut用例。文件中区分了test_case_math_opstest_case_nn_opstest_case_array_opstest_case_other_ops等用例列表,具体而言ParallelConcat算子在test_case_array_ops用例列表中添加

2. 算子python侧前端接口开发

① mindspore/ops/operations/array_ops.py文件

• 定义算子名:

• 编写初始化_init_方法:

② mindspore/ops/operations/_init_.py文件

• 导入算子接口。

3. 算子C++侧前端接口开发

① mindspore/core/ops/parallel_concat.h文件

• 声明算子类与接口函数:

• 定义字符串常量表达式,内容为算子名称:

• 定义类的构造函数,添加输入输出的Name:

• 定义类的析构函数:

宏定义MIND_API_BASE_MEMBER ()

• 定义算子推理函数:

② mindspore/core/ops/parallel_concat.cc文件

• 实现类外算子推理函数:

• 实现InferShape()函数,对输入的Shape进行校验并推导输出Shape:

• 实现InferType()函数,对输入的Type进行校验并推导输出Type:

• 宏定义REGISTER_PRIMITIVE_EVAL_IMPL用于注册算子推理函数:

③ mindspore/core/base/core_ops.h文件

• 定义PrimitivePtr:

4. 算子cpu_kernel底层核函数开发

① mindspore/ccsrc/plugin/device/cpu/parallel_concat_cpu_kernel.h文件

• 注册算子,CPU算子类继承于NativeCpuKernelMod,并创建在mindspore/kernel 命名空间下:

• 定义构造函数和析构函数:

• 声明InitKernel和Launch函数:

• 使用override 定义类保护函数GetOpSupport,用于框架获取可支持的数据类型:

② mindspore/ccsrc/plugin/device/cpu/parallel_concat_cpu_kernel.cc文件

算子实现,算子具体实现类是继承自 NativeCpuKernelMod 类的子类,需根据算子实际情况,重写 InitKernel ()、和 Launch()等方法,主要功能如下:

• 实现InitKernel ()函数,进行算子输入输出 shape 和属性值的获取、format 转换,并根据 shape 信息,计算输入输出以及 workspace 的内存需求:

• 实现Launch()函数,完成算子核心功能的调用执行:

• 实现LaunchKernel()模板函数,实现算子的计算逻辑:

• 实现GetOpSupport()函数,注册算子所支持的数据类型,对于各种可变类型组合,需要进行补齐,详细方法可以参考当前库里源码:

• MS_KERNEL_FACTORY_REG注册宏将算子类注册到工厂中:

5. 交付件准备

(1) 算子接入设计文档

算子接入设计文档主要包括算子Python侧接口描述与功能介绍,算子输入输出参数说明,算子Infer推导流程,算子异常处理。这些内容本申请书均已包含,故只需要稍加修改即可。

(2) so文件

MindSpore CPU正向算子开发需要交付算子的运行so文件,具体而言ParallelConcat算子so文件为:libcpu_kernels_ParallelConcat.so该文件需要向导师领取,再将其上传到测试服务器中,经过编译后会自动将其存储到conda/envs/xxx/lib/python3.7/site-packages/mindspore /lib/路径下,以支持该算子在CPU环境下的运行和测试。

(3) doctest

doctest是用于检测算子接口注释是否符合规范以及样Example输出是否正确,其具体流程如下:

① 进入自己conda环境中的mindspore包:

cd /xxx/xxx/.conda/envs/xxx/lib/python3.7/site-packages/mindspore

② 在当前路径传入conftest.py文件

③ 修改conftest.py文件中导入的operation类型:

④ 进入算子python侧前端接口定义目录:

cd /xxx/xxx/.conda/envs/xxx/lib/python3.7/site -packages/mindspore/ops/operations

⑤ 在当前目录下传入需要doctest的算子接口文件array_ops_parallel_concat.py

⑥ ParallelConcat算子的前端定义复制粘贴到上面创建的array_ops_ parallel_concat.py文件中,并且要增加支持的平台类型

⑦ 在此路径下执行doctest命令:

pytest --disable-warnings -vra --doctest-modules -o doctest_optionflags=NORMALIZE_ WHITESPACE --tb=long array_ops_ parallel_concat.py

⑧ 如果通过则输出绿色passed,如果报错根据报错信息修改注释

(4) 接口注释网页自验证

接口注释网页自验证是为了能够直观检查算子合入后的前端接口文档是否存在错误以及是否满足规范。其具体流程可参照:接口注释网页自验证指南

其具体流程如下:

① 下载MindSpore Docs仓代码:这里注意,由于官方指南为旧版本,因此其中一些文件路径对应不上,因此建议clone旧版的Docs仓代码,例如:

git clone -b r1.6 https://gitee.com/mindspore/docs.git

进入api目录,安装该目录下requirements.txt文件中的依赖项

cd docs/mindspore/api

pip install -r requirements.txt

③ docs仓中添加新增算子接口名

ops接口在docs/mindspore/api/source_zh_cn/api_python/operations.rst文件中添加。需要注意算子对应功能的类别,具体而言ParallelConcat算子需要在Array Operators中增加一行新增接口mindspore.ops.ParallelConcat

④ api目录下执行如下命令,完成后会新建build_zh_cn/html目录,该目录中存放了生成后的文档网页

make html

⑤ 下载完整的build_zh_cn/html包,并使用浏览器打开index.html文件在其中找到mindspore.ops.ParallelConcatt算子的前端接口文件并截图保存。

(5) 测试报告

测试报告主要依照第5小节的测试用例来编写,需要包括测试用例的数量,测试用例覆盖的shapedtype情况,并且需要包含Graph静态图和Pynative动态图两个模式的测试结果。最后需要填写测试报告表,对于表中列举的输入,输出以及属性测试结果进行填写,并在备注栏附加测试用例名,方便测试人员对照测试。

四、 参考文献

[1] 开源之夏-昇思MindSpore CPU正向算子开发:ParallelConcat项目详情页:

https://summer-ospp.ac.cn/#/org/prodetail/221cb0311

[2] MindSpore官网:

https://www.mindspore.cn/

[3] MindSpore CPU算子开发指南:

https://gitee.com/david-he91/mindspore/wikis/MindSpore%E7%AE%97%E5%AD%90%E4%BC%97%E6%99%BA/CPU%E8%AE%A1%E7%AE%97%E7%AE%97%E5%AD%90%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%97/01%20%E4%BD%BF%E7%94%A8%E5%89%8D%E5%BF%85%E8%AF%BB

[4] MindSpore官方Gitee代码托管仓库:

https://gitee.com/mindspore/mindspore

[5] MindSpore官方Github代码托管仓库:

https://github.com/mindspore-ai/mindspore

[6] MindSpore官方论坛:

https://bbs.huaweicloud.com/forum/forum-1076-1.html

[7] PyTorch官方文档:

https://pytorch.org/docs/stable/index.html

[8] PyTorch中文文档:

https://pytorch-cn.readthedocs.io/zh/latest

[9] PyTorch官方Github代码托管仓库:

https://github.com/pytorch/pytorch

[10] TensorFlow官方文档:

https://tensorflow.google.cn/versions

[11] TensorFlow中文文档:

https://tensorflow.google.cn

[12] TensorFlow官方Github代码托管仓库:

https://github.com/tensorflow

[13] MindSpore ParallelConcat算子接口文档:

https://www.mindspore.cn/docs/api_python/ops/mindspore.ops.ParallelConcat.html

[14] MindSpore ParallelConcat GPU算子开发Issue

https://gitee.com/mindspore/mindspore/issues/I4ZZSB?from=project-issue

[15] MindSpore ParallelConcat GPU算子开发PR

https://gitee.com/mindspore/mindspore/pulls/41299

[16] MindSpore ParallelConcat TBE算子开发PR

https://gitee.com/mindspore/min d spore/pulls/2811/files

https://gitee.com/mindspore/mind s pore/pulls/3029/files

[17] MindSpore Concat算子接口文档:

https://www.mindspore.cn/docs/api/zh-CN/r1.6/api_python/ops/mindspore.ops.Concat.html#mindspore.ops.Concat

[18] MindSpore Concat AICPU算子开发Issue

https://gitee.com/mindspore/mindspore/issues/I48O7Z?from=project-issue

[19] MindSpore Concat AICPU算子开发PR

https://gitee.com/mindspore/mindspore/pulls/26772/files

[20] MindSpore CPU算子交付件模板下载链接:

https://gitee.com/david-he91/mindspore/wikis/MindSpore%E7%AE%97%E5%AD%90%E4%BC%97%E6%99%BA/%E4%B8%80%E7%AB%99%E5%BC%8F%E5%AF%BC%E8%88%AA

[21] MindSpore CPU算子交付件doctest以及网页接口自验证指南:

https://gitee.com/david-he91/mindspore/wikis/MindSpore%E7%AE%97%E5%AD%90%E4%BC%97%E6%99%BA/Ascend%E8%AE%A1%E7%AE%97%E7%AE%97%E5%AD%90/Ascend%E8%AE%A1%E7%AE%97%E7%AE%97%E5%AD%90%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97/04.%20%E7%AE%97%E5%AD%90%E5%89%8D%E7%AB%AF%E5%AE%9A%E4%B9%89

[22] MindSpore CPU算子交付件动态shape测试dynamic_test测试用例编写指南:

https://gitee.com/david-he91/mindspore/wikis/MindSpore%E7%AE%97%E5%AD%90%E4%BC%97%E6%99%BA/%E5%8A%A8%E6%80%81Shape%E7%AE%97%E5%AD%90/%E5%8A%A8%E6%80%81Shape%E7%AE%97%E5%AD%90%E9%AA%8C%E6%94%B6%E6%8C%87%E6%A0%87%E8%AF%B4%E6%98%8E

[23] MindSpore CPU算子代码与交付件自验证checklist链接:

https://gitee.com/mind_spore/dashboard/wikis/mind_spore/ascend-operator-checklist/preview?sort_id=4249770&doc_id=1531116

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。