基于MindStudio的R2U_NET模型离线推理全流程任务介绍

1 任务介绍

R2U-Net是基于U-Net模型的循环残差卷积神经网络 (RRCNN)。所提出的模型利用了U-Net、Residual Network以及RCNN的强大功能。这些提议的架构对于分割任务有几个优点。首先,残差单元有助于训练深度架构。第二,具有循环残差卷积层的特征积累确保了分割任务更好的特征表示。第三,它允许我们设计更好的 U-Net 架构,具有相同数量的网络参数,具有更好的医学图像分割性能。所提出的模型在三个基准数据集上进行了测试,例如视网膜图像中的血管分割、皮肤癌分割和肺病变分割等。

本案例主要介绍如何利用获取的PyTorch版本的R2U_NET 模型的数据和模型,在MindStudio中进行模型的转换和SDK的开发部署。参照此案例,可以实现R2U_NET模型在昇腾设备的部署。

2 MindStudio介绍和安装

2.1 介绍

MindStudio是一套基于华为自研昇腾AI处理器开发的AI全栈开发工具平台,其功能涵盖面广,可以进行网络模型训练、移植、应用开发、推理运行及自定义算子开发等多种任务。就使用而言,整体界面风格和python开发常用的PyCharm相似。熟悉PyCharm工具的开发者专用MindStudio进行开发能够很快上手。

2.2 安装

MindStudio的安装已经在其官网上的用户手册中有了详细介绍。本案例中使用的操作系统为Ubuntu18.04,因为参照安装指南下的Linux安装操作进行安装。总的来说没有太多坑,但是有三个地方值得注意一下。

MindStudio和CANN的版本对应关系:在MindStudio下载界面的最下面的版本配套中就说明了当前MindStudio版本对应的CANN版本。

在下载的时候CANN需要注意一下,因为可能CANN更新较快,直接点进CANN的主页的时候,对应的不一定就是下载的MindStudio的所需要的CANN版本,可能需要在CANN的历史版本中去寻找。

      环境依赖安装:Linux环境安装的时候需要安装一些依赖,教程提供的安装方式是以pip3进行,比如pip3 install numpy。但是在实践过程中对于部分软件包,如pip3 install protobuf会遇到报错command "python setup.py egg_info" failed with error code 1。网上解决方案基本都是升级pip,但是实践的时候发现并不能解决问题,最后是通过修改pip3 为pip安装解决了问题,如:pip install protobuf,原因未知。

Python版本问题: python版本为3.7x就行,但是我实际在系统中以python3.7.3进行安装的时候会遇到如下问题:

      image.png

在网上查到了两个解决方案,一个是需要新建用户HwHiAiUser,看着就很不靠谱,还有一个则是走一遍python3.7.5的配置过程。按照这个处理之后便成功安装了。当然也不一定是版本问题,也可能是这个过程中的某个步骤带来的操作,不过如果安装中出现类似的问题,可以就按照上述的第二个解决方案解决。解决方案具体如下图所示

image.png

3 推理数据准备

3.1 介绍

至此,我们已经有了可以正常打开运行的MindStudio工具。接下来就需要准备模型转换和SDK开发所需要用到的数据。主要包括三个内容:模型源码,预训练模型,以及测试的数据。

3.2 模型文件

点击链接,进入R2U_NET的主页,点击下载模型脚本和下载模型,获取R2U_NET所需要的相关文件。

下载后,源码目录如下:

image.png

模型文件如下:

image.png

模型的目录细节可以参见下载界面左侧的:准备工作—源码介绍。下载完成后,将模型目录中的 R2U_Net-150-0.0002-70-0.4000.pkl 文件复制到代码目录下,和代码和infer文件夹同级。

3.3 数据集下载

下载链接中下载ISIC2018 task1的数据,将下载好的数据一同放在代码目录下,和代码和infer文件夹同级

4 工程创建

4.1 介绍

相比于PyCharm,MindStudio增加了对华为昇腾系列芯片相关的开发支持,所以在MindStuio中创建的工程可以简单分为昇腾工程和非昇腾工程,其中昇腾工程特指需要昇腾芯片的相关工程,包括训练,推理,算子开发等等。特别的,MindStudio还支持非昇腾项目到昇腾项目的一键转换,方便其他IDE项目的转移。本项目的目的是在昇腾310推理服务器上实现R2U_NET模型的部署推理,因此需要创建为昇腾项目。

4.2 工程创建

首先,直接在MindStudio中点击左上角的file->open 打开之前下载下来的模型代码文件夹。工程界面和目录如下:

image.png

目前工程只是一个非昇腾工程,单击菜单栏 “Ascend > Convert To Ascend Project”,如下图所示

image.png

 弹出如下窗口。这里针对R2U_NET网络,Project Type选择Ascend App,Sub Type选择Ascend Python ACL App。如下图

image.png

完成工程转换后,可以看到在菜单栏多出了一栏蓝色的图标,这就是MindStudio为昇腾系列所特别支持的开发功能。

image.png

5 本地环境配置

5.1 介绍

目前我们有了一个在linux本地端转换完成的昇腾项目。但是要想在本地进行一些代码的开发,如模型转换还需要在本地配置相应的环境,具体来说就是代码原始的工作环境,比如R2U_Net是PyTorch代码,那么就需要配置PyTorch的环境。

5.2 环境配置

点击File->Project Structure打开如下的界面。选择platform settings 下的SDK,点击“+”号添加python SDK,根据自己的情况从不同的地方添加Python’ SDK。这里的Python SDK 其实就是项目python的运行环境,跟我们常用的虚拟环境其实是类似的概念。这里因为我本机已经装有PyTorch环境,所以直接从conda中选择路径添加就行。如果没有的话可以自己创建一个新的,创建过程就是普通的conda 创建虚拟环境过程,不需要在MindStudio中进行,可以用自己熟悉的方式,只要最后电脑中有了这个虚拟环境就行,然后在MindStudio中再选中添加即可。

image.png

至于如何添加呢?刚刚只是把虚拟环境加载到了MindStudio中,也就是说目前MindStudio可以找到这些环境,但是对于具体的项目需要用到哪个环境需要在Project Settings 下的Project中进行设置,这里选择为我刚刚添加的PyTorch环境。

image.png

至此,本地环境配置就完成了一半,而另一半则是动态库路径的添加。简而言之就是,我们虽然在本地装载了CANN,但是实际的昇腾工程并没有完全把所有CANN的链接库添加到工程的环境变量中,所以在代码运行的时候可能会出现部分链接库找不到的情况,这时候根据具体的情况再添加就行。具体情况在模型转换中会有介绍。

6 远端服务器配置

6.1 介绍

目前完成了本地的环境配置,可以在本地进行一些简单的开发和debug,但是因为我们目前是在非昇腾设备上安装的MindStudio和Ascend-cann-toolkit开发套件包。作为开发环境仅能用于代码开发、编译等不依赖于昇腾设备的开发活动(例如ATC模型转换、算子和推理应用程序的纯代码开发。只是开发,不能调试和运行)。如果想运行应用程序或进行模型训练等,需要通过MindStudio远程连接功能连接已部署好运行环境所需软件包的昇腾AI设备.

简单来说,昇腾AI设备在这里可以看做深度学习中的服务器,而我们的电脑就是本地主机。一般我们都是在本地主机上开发,然后把代码上传服务器运行,是不能在本地运行代码的。而配置服务器就是化简了这个过程,使得这个过程可以自动化,在运行代码的时候代码会自动上传到服务器并运行,并且远端运行的结果和日志还会自动传输到本地显示,避免了手动操作。整体体验就像是在本地进行开发编译和运行一样

6.2 服务器配置

如果熟悉PyCharm的远端服务器配置,那么在MindStudio中的配置就会非常容易,同样都是在 tools/development/configure/下进行配置,配置流程也和PyCharm中一致。如果不熟悉PyCharm,请参照这篇教程中第4步,推理实践中的指导进行分别配置SSH, Development,里面已经有了详细的介绍。

整个配置过程中基本不存在什么问题。只需要注意在配置过程中注意配置exclude path,将远端路径的数据文件夹排除在外,避免传输数据浪费时间。

在完成服务器配置之后,点击 tools->development->upload 上传模型和代码到服务器。而数据则先暂时不传输,因为目前获取的数据还没有划分训练,验证,测试集,需要在本地端划分之后再单独把测试集上传即可,这样可以减少数据传输的时间。具体的操作在后面的模型推理测试中会介绍。而现在需要把模型和代码上传到远端服务器是因为后续的模型转换有一个做法就是在远端服务器进行

7 模型转换

7.1 介绍

一般PyTorch工程最后会得到一个预训练好的pkl文件,但是实际要部署部署的设备可能并不支持这个格式的模型,比如昇腾设备仅支持om模型,因此需要将pkl模型转为om模型。而目前并无相关的库可以直接支持将pkl转为om模型,因此我们需要先将pkl模型转为onnx模型,再将onnx模型转为om模型。

因为这部分并没有涉及到具体的模型运行的过程,所以可以在本地,使用MindStudio工具就完成。

7.2 pkl模型转onnx模型

下载的工具包中已经提供了pkl转onnx模型的代码pthtra2onx.py,直接运行代码就行。打开pthtra2onx.py,右键,然后选择运行就行,和普通的pycharm开发中的代码运行没有任何差异,成功运行后,在工程目录中就会出现一个R2U_Net.onnx模型。

7.3 onnx转om文件

onnx转om有两种实现方式,一种是利用infer/convert下的onnx2om.sh脚本文件,另一种就是利用MindStudio对昇腾开发的支持model convert.下面将会分别介绍两种方式。

脚本转换方式。一样的运行方式,打开onnx2om.sh 脚本,然后右键,运行。此时会报如下错误:

/home/shicaiwei/Ascend/ascend-toolkit/5.1.RC1/x86_64-linux/bin/atc.bin: error while loading shared libraries: libascend_hal.so: cannot open shared object file: No such file or directory

而我们在ascend-toolkit中搜索发现libascend_hal.so在/home/shicaiwei/Ascend/ascend-toolkit/latest/x86_64-linux/devlib中,所以我们选择运行框中的onnx_2om.sh,然后选择Edit configuration, 然后点击环境变量后面的按钮。

image.png

找到LD_LIBRARY_PATH, 在后面把” /home/shicaiwei/Ascend/ascend-toolkit/latest/x86_64-linux/devlib” 添加进去。

image.png

然后保存。

image.png

再点击运行,会出现Value for [--model] is empty.,这是因为这个bash文件需要指定onnx模型和路径和转换后模型的名字,但是目前没有指定。我们重新点击onnx2om的Edit configuration,在Script options 后面依次添加onnx模型路径和转换后的名字.路径的添加可以是相对路径也可以是绝对路径,如果是相对路径的话,要以当前的工作路径

” /home/shicaiwei/project/R2U_NET_Huawei/SDK/test/infer/convert”为基准,具体来说就是  ../.. /R2U_Net.onnx  ../../R2U_Net

然后点击运行即可。如果还是运行失败,则可能是本地环境问题,我遇到的情况是在一开始是可以本地运行的,后来不知道怎么又不行了。暂时没有解决好。这时候还有最后一个使用脚本的方法,即直接在远端服务器执行生成。

远端命令执行:点击tool->start ssh session,打开远程服务器,进入本地代码在远端服务器对应的map path。

进入“convert”目录,,执行以下命令,进行模型转换。

bash onnx2om.sh onnx_path om_path

转换详细信息可查看转换脚本,在“onnx2om.sh”脚本文件中,配置相关参数

参数

作用

onnx_path

onnx模型的路径

om_path

生成的om模型的存储路径

在本项目中命令示例如下:bash onnx2om.sh ../.. /R2U_Net.onnx ../../R2U_Net

最后,MindStudio 工具转换:在菜单栏选择“Ascend > Model Converter”

image.png

配置模型信息

image.png

image.png

在转换过程中,请注意不要选择默认的FP16,而是选择FP32,否则在推理过程中可能会带来精度的损失。其次, actual_input1 中的batchsize保持默认值1就可以。

单击Finish开始模型转换,在MindStudio界面下方,“Output”窗口会显示模型转换过程中的日志信息,如果提示**“Model converted successfully”**,则表示模型转换成功。“Output”窗口会显示模型转换所用的命令、所设置的环境变量、模型转换的结果、模型输出路径以及模型转换日志路径等信息

image.png

8 模型推理测试

8.1 介绍

完成模型转换,得到om文件之后,如何快速的验证获取的om模型的精度呢?MindStudio提供了两种方法,一种是benchmark工具,可以利用转换之后的om模型和预处理数据,快速的完成推理并得到结果。还有一种就是编写SDK推理代码,实现SDK推理流程。二者的区别就在于在什么地方实现数据的预处理,如果使用benchmark工具,那么需要提前处理好数据,得到.info 文件作为benchmark的输入,而SDK推理则是把数据预处理集成到了pipeline中,可以直接以原始的图像数据作为输入。这里以SDK为例子,来展示模型推理过程。

整个推理过程包含五个过程,软硬件配置,数据集生成,模型上传,SDK开发,模型推理。

8.2 软硬件需求配置

基于MindX SDK的总体结构如下:

image.png

从上图中可以看到,要进行MindX SDK需要分别准备好如下的软硬件需求。

硬件:因为Mindx SDK是转为加速华为昇腾系列处理器进行深度学习开发的工具。需要一块昇腾推理器,可以是华为官方提供也可以是自己购买的。这里选择的华为提供的昇腾推理服务器。

软件:软件的准备包含两个内容,即在昇腾设备上安装异构计算架构CANN,和SDK插件,插件根据需求选择mxVison或者mxManufacture。

关于CANN,对比MindStudio安装中用到的CANN有什么联系呢?安装MindStudio时候安装的CANN属于在非昇腾场景下安装CANN,而这里在昇腾设备上安装CANN是昇腾场景下安装CANN的流程,需要额外针对不同昇腾设备安装不同的固件和驱动,具体可以参见CANN安装指导。因为提供的昇腾服务器已经完成了CANN的安装,所以我们略过这个步骤。

关于SDK,我们的开发任务属于视觉任务,所以选择安装mxVison的SDK插件。请注意,SDK的安装同样分为昇腾设备和非昇腾设备安装,或者说远端环境安装和本地安装,其中远端其实指的就是昇腾设备,而本地一般指的就是非昇腾设备。为什么需要远端和本地都安装呢?远端作为最终的运行环境,必须要有完整的底层支撑,所以安装CANN和SDK很好理解,而本地端则是开发需求,虽然最终的程序并不是在本地端进行的,但是开发过程一般是在本地端进行的,所以为了方便开发,比如代码补全,代码静态分析,本地编译,等功能,本地也需要配置有和最终运行环境一样的硬件无关的环境,也就是CANN和SDK,而和硬件相关的,比如固件和驱动则不需要也无法在本地安装。

那么本地怎么安装SDK呢?如果本地是Windows,是选择通过远程连接直接将远程的SDK复制到本地的方式,而如果本地是Linux系统则是直接联网安装就行,二者都没有什么难度,详情见Mindx SDK 安装。重点是理解本地和远端以及为什么本地和远端都需要安装SDK。

8.3 数据集生成

  1. 提供的数据集并没有划分训练,测试,需要我们自己手动划分。在本地运行python3 dataset.py -- origin_data_path  ISIC2018_Task1-2_Training_Input --origin_GT_path ISIC2018_Task1_Training_GroundTruth 生成训练,验证和测试数据集。

image.png

  1. 将测试数据集上传到远端服务器。这里为了加速数据传输过程,可以考虑在development中把train 和valid 数据的路径放在exclude path里。上传完成后把整个dataset路径都放在exclude path中。因为每次运行代码的时候都会把本地的数据同步到远端,这样子可以加速同步的过程。但是请不要直接删除,不要直接删除,不要直接删除!!!, 代码数据同步的时候,是以本地端为准的,完成覆盖远端的映射文件夹,如果是本地没有而远端有的,会直接删除掉,所以如果本地把数据删除了,远端也会删除。

8.4 模型上传

完成了数据的准备和上传后,我们要进一步上传转换得到的om模型到远端。这里选择的路径是infer/data/models 文件夹。传输流程和数据传输是一样的,上传模型,然后把模型路径设置到exclude path中。

8.5 SDK开发

目前,我们已经完成了本地和远端环境的CANN和SDK配置,同时也完成了数据和模型的上传,接下来就可以进入到具体的SDK的开发过程中。因为是完全针对昇腾芯片做的适配,所以对应的封装接口非常high-level, 很方便调用来设计自己的SDK应用。接下来会以基于python的R2U_NET的SDK开发为例子介绍整个过程。

8.5.1 获取开发模板

在顶部菜单栏中选择File > New > Project...。在New Project窗口中,选择Ascend App,按下图配置工程。

image.png

然后点击next

image.png

以下两项为MindX SDK空白工程,仅包括开发框架的工程,不含具体的代码逻辑:MindX SDK Project(C/C++)MindX SDK Project(Python)。以下两项为MindX SDK样例工程,基于MindX SDK开发的模板工程:Detection and Classification(C++)Detection and Classification(Python)。这里我们需要选择Detection and Classification(Python),同时打开一个新的窗口。这样就得到了一个python的SDK工程模板,如下图所示:

image.png

对于这个模板,我们取出其中的python文件夹中的main.py就得到了一个包含完整SDK项目配置的代码文件,利用这个代码文件,我们可以非常方便的针对自己的具体项目做修改和适配。具体来说,这份代码是实现了4个主要功能,利用InitManager()方法创建流管理工具,利用CreateMultipleStreams()方法和pipeline文件创建特定功能的流,利用SendDataWithUniqueId()方法把数据输入流中,再利用GetResultWithUniqueId从流里面读取结果。这里的“流”可以理解为数据流或者处理流,代表一些列操作的封装集成模块。

从中我们可以看到,对于特定的SDK项目,我们只需要pipeline路径到对应功能的pipeline文件,再把输入改成对应的目标输入,就完成了对特定项目的适配。

8.5.2 pipeline文件设计

pipeline,在这里其实就是指一系列的数据预处理处理,模型推理,和后处理插件组成的处理管道。明白了它的本质之后,整个设计就很简单,确定PyTorch/TensorFlow/MindSpore源码中数据预处理过程,然后在pipeline的插件库中找到每一个过程比如Resize,randomCut,的对应插件,按照原来的顺序组装起来就行。至于如何组装,可以参见流程编排介绍

在这个过程中可能会遇到一个问题就是如果源代码中的处理过程没有对应的插件可以使用怎么办?这时候有两种办法,一种就是自己写一个插件实现对应的功能,具体可以参见插件开发介绍。还有一种办法就是完全不使用pipeline的预处理插件,自己利用numpy或者opencv重写所有的预处理代码,然后数据在pipeline对应的流外处理好之后直接利用数据读取接口读入进行推理和后处理,然后获取结果。因为大部分的预处理底层都是利用opencv或者numpy接口实现的,所以利用两个库的重写难度其实也不是很高。

其中R2U_Net选择的就是后一种方式,所以它对应的pipeline文件就非常简单,只有四个模块,输入读入接口,数据推理,数据格式转换,数据输出接口。具体如下图所以:

image.png

8.5.3 数据模块设计

完成了pipeline文件设计后,我们还要对数据输入接口做一定的修改。首先最开始的main.py中设计的是输入默认是test.jpg数据。而这里我们要测试整个数据集,所以增加一个for循环遍历输入数据。其次之前的main.py默认是把预处理放在pipeline里面,但是现在是要用代码实现,所以读入数据后,还要经历预处理函数的处理,然后再将结果送入流中处理。最后,因为我们还要做模型推理的评估,所以还要加上结果的分析函数,这部分也是直接仿着源代码利用numpy进行重写就行。

至此,就完成了整个SDK的开发流程。并且得到了适应于R2U_NET项目的main.py函数,位于项目中的infe/sdk/main.py处。此时可以开始进行模型推理测试了。

8.6 模型推理

首先,我们需要把main.py中pipeline文件的路径修改为自己的pipeline文件的路径,这一步可以通过infer/sdk/main.py 中的pipeline_path 实现,默认为“../data/models/R2U_Net.om”。然后修改数据路径,这一步可以通过infer/sdk/main.py 中的data_path 实现,默认是“../../dataset/”。

然后在MindStudio工程界面,依次选择“Run > Edit Configurations...”,进入运行配置页面。选择“Ascend App > 工程名”配置应用工程运行参数,图1为配置示例。配置完成后,单击“Apply”保存运行配置,单击“OK”,关闭运行配置窗口。

image.png

参数说明如下

image.png

我们选择远端运行,但是可执行文件而是就选择本地的可执行文件就可以,也就是infer/sdk/main.py。然后整个工程同步上传到远端后,程序会自动选择远端的这个文件运行。并不是在这里填上远端的运行路径!!!。

      成功运行后可以看到如下的提示:

image.png

      再去log中寻找代码输出的测试准确率。可以看到测试准确率为0.948,略高于PyTorch源码中的0.946,顺利完成了迁移。

image.png

9 FAQ

9.1 连接VPN可以连接指定的开发服务器如192.168.88.213,却无法连接到其他服务器如90.90.66.16的推理服务器。

检查VPN最后的路由设置,是否把新的ip地址添加了使用vpn的地址列表中。

image.png

9.2 模型转换问题

  1. 我的onnx模型的输入格式是 NWHW. 做ATC转换的时候也是input_format=NCHW, 为什么转换出来的om模型的输入是 NHWC格式的
    1. 这是因为经过aipp处理带来的,所有经过aipp处理的模型的格式都是NHWC,通道位于最后的位置,所以要么后续的数据处理都是按照这个进行,要么就不要使用aipp配置
  2. onnx 模型读取失败和netron分析失败,magic_number = pickle_module.load(f, **pickle_load_args),EOFError: Ran out of input
    1. 可能是因为传输过程带来的模型损坏,需要重新传输。

9.3 SDK开发过程的一些问题

  1. 提示没有cv2,但是pip install opencv-python的时候又会提示说Requirement already satisfied: opencv-python in /usr/local/python3.7.5/lib/python3.7/site-packages (4.6.0.66)?
    1. 这是因为远端服务器安装了两个python3版本,python3.9和python3.7,而默认指向的是python3.9,但是之前opencv是安装在python3.7的第三方库下的,所以检索不到,报错找不到cv2, 但是pip 安装又会默认检索所有的第三方库,所以提示已经安装了,这时候可以修改为pip3 install opencv-python 解决问题。或者修改服务器python3的默认链接指向python3.7。这里选择了前者,因为最新的SDK只能在python3.9下运行,所以为了保证兼容性,不修改默认链接指向python3.7
  2. ModuleNotFoundError: No module named 'google'
    1. 不是pip3 install google, 而是需要pip3 install protobuf 
  3. TypeError: Descriptors cannot not be created directly.If this call came from a _pb2.py file, your generated code is out of date and must be regenerated with protoc >= 3.19.0.
    1. pip3 install protobuf==3.19.0,降低protobuf版本。
  4. opencv.resize 和torch.resize 处理的结果不一致
    1. PyTorch使用的是PIL进行图像的预处理,其中resize函数的实现和opencv有所不同,即时都是指定resample 参数为双线性插值,也还是会存在结果差异,不过幸运的是这个差异并不会对结果带来很大影响,波动在0.1%

9.4 Modelart开发过程中的一些问题

  1. Raise NotImplementedError(custom_err_msg)NotImplementedError:amp does not work out-of-the-box with `F.binary_cross_entropy` or `torch.nn.BCELoss.` It requires that the output of the previous function be already a FloatTensor.Most models have a Sigmoid right before BCELoss. In that case, you can usetorch.nn.BCEWithLogitsLossto combine Sigmoid+BCELoss into a single layer that is compatible with amp.
    1. 按照提升应该需要修改solver.py 中的torch.nn.BCELoss 为torch.nn.BCEWithLogitsLoss,但是要求又是不能修改train_strat.py之外的其他文件,最后找到的解决办法是修改train_strat.py apex-opt-level的设置,修改为‘02’,而不是之前的默认参数‘01’。
  2. ModuleNotFoundError: No module named 'torch'
    1. 参照别人使用自定义框架,解决问题,发现一般是引擎问题,PyTorch模型的训练并不是像modelarts的教程里面教导的一样,使用系统自带的PyTorch引擎,而需要使用自定义的引擎。
  3. untimeerror:initialize:/usr1/workspace/fpta_daily_open_2.0.3.tr5/code/PyTorch/c10/npu/sys_ctrl/npu_sys_ctrl.cpp:49 npu error
    1. 一般是npu变号问题,设置为0,而不是1.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值