• [活动体验] MindSpore性能调试设计
    性能调试设计为了直观地展现网络模型各维度的性能信息,为我们提供易用、丰富的性能分析功能,帮助我们快速定位网络中性能问题。Profiler架构设计这一章将介绍Profiler的架构设计,第一节从整体Profiler的角度出发介绍其上下文交互关系,第二节将打开Profiler内部,介绍模块层架结构以及模块划分,第三节将介绍模块间的交互调用关系。在整个使用过程中的上下文环境如下图所示:上图所示,Profiler与其他部分的交互包括:在训练脚本中调用MindSpore的Profiler启动收集性能数据的命令,Ascend类型下由ada模块生成性能原始数据,GPU类型下由CUPTI模块生成性能原始数据;MindSpore侧Profiler将在用户脚本中对原始数据进行解析,并在用户指定的文件夹下面生成中间数据结果;Mindinsight侧Profiler对接中间数据,提供可视化Profiler功能供用户使用。模块层级结构模块层级划分如下层级模块关系图如上图所示,各个模块功能介绍如下:ProfilerAPI是代码侧为用户提供的调用入口,为用户提供了性能收集启动接口以及分析接口;Controller是ProfilerAPI下层的模块,被ProfilerAPI中的启动接口调用,负责控制下方性能收集功能的启动停止,原始数据会被写入固定位置;Parser是性能原始数据解析模块,由于性能原始数据是在设备侧收集的信息,所以信息不能直接被用户所理解,该模块负责将信息进行解析、组合、转换,最终形成用户可理解、上层可分析的中间结果;Analyser获取下层Parser解析出的中间结果,负责对中间结果进行封装、筛选、排序,最终按照信息分类,返回各个类别对应的信息,提供给上层的表现层Profiler API、RESTful使用;通过RESTful调用后端Analyser提供的common API,获取目标数据,以RESTful接口对接前端。内部模块交互从用户角度,有两种使用形式API、RESTful,我们以API为例,阐述一个完整的内部模块交互流程:模块交互图如上图所示,个模块交互流程如下:ProfilerAPI会调用下层Controller的控制函数,控制下层收集模块进行收集,Ascend收集模块为ada,GPU为CUPTI用户在训练结束后会调用ProfilerAPI的分析接口;Profiler API分析接口首先使用Parser模块对性能数据进行解析,产生中间结果,再调Analyser进行中间结果分析,最终将各类信息返回至用户侧。子模块设计ProfilerAPI和ControllerProfilerAPI和Controller模块说明ProfilerAPI为用户在训练脚本侧提供入口API,用户通过ProfilerAPI启动性能收集以及对性能数据进行分析。 ProfilerAPI通过Controller下发命令,启动性能数据收集模块。ProfilerAPI和Controller模块设计ProfilerAPI模块,属于上层应用接口层,由训练脚本集成。功能分为两部分:训练前调用底层Controller接口,下发命令,启动profiling统计任务。训练完成后,调用底层Controller接口,下发命令,停止性能统计任务,再调用Analyser、Parser模块接口解析数据文件,生成算子性能统计、training trace统计等结果数据。Controller模块提供对上层接口,并调用底层性能收集模块接口,下发启动和停止性能收集的命令。最终生成的性能原始数据主要包含:Ascend:hwts.log.data.45.dev.profiler_default_tag文件:存储算子执行信息,包括task的开始/结束,stream id的信息等;DATA_PREPROCESS.dev.AICPU文件:AI CPU算子的执行各阶段的执行时间信息;Framework.host.task_desc_info文件:存储算子id与算子名称的对应关系,以及每个算子的输入输出信息;training_trace.46.dev.profiler_default_tag文件:存储每个step的开始结束时刻,迭代间隙、迭代前向反向、迭代拖尾的时刻信息。GPU:step_trace_profiling_0.txt文件:存储了前向/反向的起止算子等信息。ParserParser模块介绍Parser是原始性能数据解析模块,由于原始性能数据是在设备侧收集的信息,所以信息不能直接被用户所理解,该模块负责将信息进行解析、组合、转换,最终形成用户可理解、上层可分析的中间结果Parser模块图如上图所示,Parser模块主要由HWTS Parser、AI CPU Parser、Framework Parser、Step Trace Parser组成,每个模块对应解析一种原始数据,通过解析原始数据得到用户能读懂的中间文件。其中 Ascend主要用到HWTS Parser、AI CPU Parser、Framework Parser、Step Trace Parser,GPU主要用到Step Trace Parser。Ascend:HWTS Parser:解析hwts.log.data.45.dev.profiler_default_tag文件,获得Device基于task的统计信息,如每个task的开始/结束,stream id等数据,用于算子执行时间的计算AI CPU Parser:解析DATA_PREPROCESS.dev.AICPU文件,获得AI CPU算子的执行各阶段的执行时间信息。Framework Parser:解析Framework.host.task_desc_info文件,用于获取AI Core算子与task的对应关系,算子关键信息等内容。Step Trace Parser:解析training_trace.46.dev.profiler_default_tag文件,用于分析训练各阶段的时间。GPU:Step Trace Parser:解析step_trace_profiling_0.txt文件,用于分析训练各阶段的时间。
  • [其他问题] modelarts平台,使用ascend910环境、kernel为mindspore的notebook运行程序,cpu使用率很低
    问题描述:在modelarts平台,使用单卡ascend910 cpu24核、kernel为mindspore的notebook运行程序,cpu使用率很低。以下是在上述环境与另一个modelarts平台 cpu2核、kernel为pytorch1.4的notebook运行相同程序时的性能对比:单卡ascend910 cpu24核、kernel为mindspore的notebook(kernel重启过后运行的):cpu利用率一直小于10%,基本出于%5的状态,程序运行时间为169.93scpu2核、kernel为pytorch1.4的notebook:(kernel重启过后运行的)cpu利用率始终大于50%,大多数时间处于95%以上,运行时间仅10.39s请问上述情况可能的原因是什么,modelarts平台,单卡ascend910 cpu24核、kernel为mindspore的notebook如何提高cpu利用率呢?
  • [执行问题] modelarts notebook ascend910 mindspore运行问题
    问题描述:在modelarts平台,使用单卡Ascend910服务器的notebook,运行命令,显示错误信息(输入命令与错误信息请见下面的截图),kernel选择的是"Mindspore"。前一天使用时kernel的字样是“mindspore-python3.7-aarch64”,运行同样的命令未显示错误信息,请问这种情况是否是kernel出现了问题,该如何解决呢?(此外,在kernel名称变为”Mindspore后“,运行相同的mindspore程序也会出现比kernel名为“mindspore-python3.7-aarch64”情况下更多的Warning信息)【截图信息】
  • [部署上线] 在基于MindSpore Lite的猫狗分类实验中模型部署到手机上没有自动生成文件夹,ms模型不知放在哪个路径下
    【功能模块】MindSpore Lite模型部署【操作步骤&问题现象】1、安装APP后手机没有自动生成PetClassification文件夹2、手动创建PetClassification文件夹,将模型置于此,APP加载模型失败【截图信息】
  • [其他问题] 【Mindspore】【AICPU算子接入】动态输入的算子还需要注册哪里
    【截图信息】【操作步骤&问题现象】st测试的时候报错如上图。 查了QA说这是信息注册未成功报的错。自查了一遍发现应该都没问题吧。。具体可看PR [assistant] [ops] Add new array operator Concat · Pull Request !26772 · MindSpore/mindspore - Gitee.com
  • [活动体验] MindSpore启动MindInsight
    训练性能我们从训练列表中选择指定的训练,点击性能调试,可以查看该次训练的性能数据。如下图所示性能的数据,在这张训练性能数据表中,展示了性能数据总览页面,包含了迭代轨迹(Step Trace)、算子性能、数据准备性能和Timeline等组件的数据总体呈现。各组件展示的数据如下:•迭代轨迹:将训练step划分为几个阶段,统计每个阶段的耗时,按时间线进行展示;总览页展示了迭代轨迹图。••算子性能:统计单算子以及各算子类型的执行时间,进行排序展示;总览页中展示了各算子类型时间占比的饼状图。••数据准备性能:统计训练数据准备各阶段的性能情况;总览页中展示了各阶段性能可能存在瓶颈的step数目。••Timeline:按设备统计每个stream中task的耗时情况,在时间轴排列展示;总览页展示了Timeline中stream和task的汇总情况。•用户可以点击查看详情链接,进入某个组件页面进行详细分析。MindInsight也会对性能数据进行分析,在左侧的智能小助手中给出性能调试的建议。迭代轨迹分析使用迭代轨迹分析组件可以快速了解训练各阶段在总时长中的占比情况。迭代轨迹将训练的一个step划分为迭代间隙 (两次step执行的间隔时间)、前向与反向执行、all reduce、参数更新等几个阶段,并显示出每个阶段的时长,帮助用户定界出性能瓶颈所在的执行阶段。如下图展示了迭代轨迹分析这张图片向我们展示了迭代轨迹分析页面。在迭代轨迹详情中,会展示各阶段在训练step中的起止时间,默认显示的是各step的平均值,用户也可以在下拉菜单选择某个step查看该step的迭代轨迹情况。页面下方显示了迭代间隙、前后向计算、迭代拖尾时间随着step的变化曲线等,用户可以据此判断某个阶段是否存在性能优化空间。其中:迭代间隙: 主要负责从数据队列中读取数据,如果该部分耗时较长,建议前往数据准备部分进一步分析。前后向计算: 执行网络中的前向算子以及反向算子,承载了一个step主要的计算工作,如果该部分耗时较长,建议前往算子统计或时间线中进一步分析。迭代拖尾: 主要在多卡场景下执行参数聚合、参数更新操作,包括前后向计算结束到参数更新完成的时间。如果该部分耗时较长,建议查看all_reduce耗时以及并行情况。
  • [活动体验] MindSpore优化过程可视化
    概述神经网络训练本质上是高维非凸函数的优化过程,一般可以通过梯度下降方法发现最小值点(如图1所示)。而一般的神经网络参数多达几万甚至几十万,较难直接在三维空间展示其优化地形。用户通过本功能,能够基于方向降维和绘制计算,将神经网络训练路径周围的优化空间展示出来。如下图所示展示的是梯度下降的方法:步骤具体使用步骤共分为两步,以LeNet为例,分类任务,数据集为MNIST,1.训练数据收集:在训练过程中,利用SummaryCollector的形式收集多个模型前向网络权重,地形图绘制所需参数(如期望绘制区间,地形图分辨率等),2.代码如下:import mindspore.dataset as dsimport mindspore.dataset.vision.c_transforms as CVimport mindspore.dataset.transforms.c_transforms as Cfrom mindspore.dataset.vision import Interfrom mindspore import dtype as mstypeimport mindspore.nn as nnfrom mindspore.common.initializer import Normalfrom mindspore import contextfrom mindspore.train.callback import ModelCheckpoint, CheckpointConfig, LossMonitor, TimeMonitor, SummaryCollectorfrom mindspore import Modelfrom mindspore.nn import Accuracyfrom mindspore import set_seedset_seed(1)def create_dataset(data_path, batch_size=32, repeat_size=1,                   num_parallel_workers=1):    """    create dataset for train or test    """    # define dataset    mnist_ds = ds.MnistDataset(data_path, shuffle=False)    resize_height, resize_width = 32, 32    rescale = 1.0 / 255.0    shift = 0.0    rescale_nml = 1 / 0.3081    shift_nml = -1 * 0.1307 / 0.3081    # define map operations    resize_op = CV.Resize((resize_height, resize_width), interpolation=Inter.LINEAR)  # Bilinear mode    rescale_nml_op = CV.Rescale(rescale_nml, shift_nml)    rescale_op = CV.Rescale(rescale, shift)    hwc2chw_op = CV.HWC2CHW()    type_cast_op = C.TypeCast(mstype.int32)    # apply map operations on images    mnist_ds = mnist_ds.map(operations=type_cast_op, input_columns="label", num_parallel_workers=num_parallel_workers)    mnist_ds = mnist_ds.map(operations=resize_op, input_columns="image", num_parallel_workers=num_parallel_workers)    mnist_ds = mnist_ds.map(operations=rescale_op, input_columns="image", num_parallel_workers=num_parallel_workers)    mnist_ds = mnist_ds.map(operations=rescale_nml_op, input_columns="image", num_parallel_workers=num_parallel_workers)    mnist_ds = mnist_ds.map(operations=hwc2chw_op, input_columns="image", num_parallel_workers=num_parallel_workers)    # apply DatasetOps    buffer_size = 10000    mnist_ds = mnist_ds.shuffle(buffer_size=buffer_size)  # 10000 as in LeNet train script    mnist_ds = mnist_ds.batch(batch_size, drop_remainder=True)    mnist_ds = mnist_ds.repeat(repeat_size)    return mnist_dsclass LeNet5(nn.Cell):    """    Lenet network    Args:        num_class (int): Number of classes. Default: 10.        num_channel (int): Number of channels. Default: 1.    Returns:        Tensor, output tensor    Examples:    LeNet(num_class=10)    """    def __init__(self, num_class=10, num_channel=1, include_top=True):        super(LeNet5, self).__init__()        self.conv1 = nn.Conv2d(num_channel, 6, 5, pad_mode='valid', weight_init=Normal(0.02))        self.conv2 = nn.Conv2d(6, 16, 5, pad_mode='valid', weight_init=Normal(0.02))        self.relu = nn.ReLU()        self.max_pool2d = nn.MaxPool2d(kernel_size=2, stride=2)        self.include_top = include_top        if self.include_top:            self.flatten = nn.Flatten()            self.fc1 = nn.Dense(16 * 5 * 5, 120)            self.fc2 = nn.Dense(120, 84)            self.fc3 = nn.Dense(84, num_class)    def construct(self, x):        x = self.conv1(x)        x = self.relu(x)        x = self.max_pool2d(x)        x = self.conv2(x)        x = self.relu(x)        x = self.max_pool2d(x)        if not self.include_top:            return x        x = self.flatten(x)        x = self.relu(self.fc1(x))        x = self.relu(self.fc2(x))        x = self.fc3(x)        return xdef train_lenet():    context.set_context(mode=context.GRAPH_MODE, device_target="GPU")    data_path = YOUR_DATA_PATH    ds_train = create_dataset(data_path)    network = LeNet5(10)    net_loss = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction="mean")    net_opt = nn.Momentum(network.trainable_params(), 0.01, 0.9)    time_cb = TimeMonitor(data_size=ds_train.get_dataset_size())    config_ck = CheckpointConfig(save_checkpoint_steps=1875, keep_checkpoint_max=10)    ckpoint_cb = ModelCheckpoint(prefix="checkpoint_lenet", config=config_ck)    model = Model(network, net_loss, net_opt, metrics={"Accuracy": Accuracy()})    summary_dir = "./summary/lenet_test2"    interval_1 = [x for x in range(1, 4)]    interval_2 = [x for x in range(7, 11)]    ##Collector landscape information    summary_collector = SummaryCollector(summary_dir, keep_default_action=True,                                         collect_specified_data={'collect_landscape': {'landscape_size': 40,                                                                                       'unit': "epoch",                                                                                       'create_landscape': {'train': True,                                                                                                            'result': True},                                                                                       'num_samples': 512,                                                                                        'intervals': [interval_1,                                                                                                      interval_2                                                                                                      ]                                                                                        }                                                                },                                        collect_freq=1)在这时需要注意的是:1. callback_fn: 用户需要定义函数callback_fn,该函数没有输入,返回model(mindspore.train.Model),network(mindspore.nn.Cell),dataset(mindspore.dataset),metrics(mindspore.nn.Metrics) 。2. collect_landscape: 参数定义与SummaryCollector一致,这里用户可以自由修改绘图参数。3. device_ids: 指定地形图绘制所需要device_ids,支持单机多卡计算。4. device_target: 指定device的类型,如GPU、Ascend或CPU。     print("============== Starting Training ==============")    model.train(10, ds_train, callbacks=[time_cb, ckpoint_cb, LossMonitor(), summary_collector])if __name__ == "__main__":    train_lenet()summary_dir设置了参数的保存路径。summary_collector为初始化的SummaryCollector实例。其中collector_specified_data中的collect_landscape以字典的形式包含了绘制地形图所需要的所有参数设置:landscape_size: 表示地形图的分辨率。40表示地形图的分辨率是40*40。分辨率越大,地形图纹理越细致,同时计算消耗时间也会越久。unit: 表示训练过程中保存参数的间隔单位,分为epoch/step。使用step时,须在model.train中设置dataset_sink_model=False。create_landscape: 表示绘制地形图的方式,目前支持训练过程地形图(带训练轨迹)与训练结果地形图(不带轨迹)。num_samples: 表示绘制地形图数据集的样本数量。512表示地形图所需样本是512。样本数越大,地形图越精确,同时计算消耗时间也会越久。intervals: 表示绘制地形图的区间。如interval_1表示绘制带训练轨迹1-5epoch地形图。地形图绘制:利用训练过程中保存的模型参数,模型与数据集与训练一致,启动新的脚本,正向计算生成地形图信息,不用再次进行训练。(适用于单卡或多卡并行计算绘制地形图)代码如下:from mindspore.nn import Lossfrom mindspore.train.callback import SummaryLandscapedef callback_fn():    network = LeNet5(10)    net_loss = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction="mean")    metrics = {"Loss": Loss()}    model = Model(network, net_loss, metrics=metrics)    data_path = YOUR_DATA_PATH    ds_eval = create_dataset(data_path)    return model, network, ds_eval, metricsif __name__ == "__main__":    interval_1 = [x for x in range(1, 4)]    interval_2 = [x for x in range(7, 11)]    summary_landscape = SummaryLandscape('./summary/lenet_test2')    # generate loss landscape    summary_landscape.gen_landscapes_with_multi_process(callback_fn,                                                        collect_landscape={"landscape_size": 40,                                                                           "create_landscape": {"train": True,                                                                                                "result": True},                                                                           "num_samples": 512,                                                                           "intervals": [interval_1, interval_2                                                                                        ]},                                                        device_ids=[1, 2],                                                        device_target="GPU")
  • [活动体验] MindSpore性能调试(Ascend)
    在Ascend AI处理器上使用MindSpore Profiler进行性能调试。流程准备训练脚本,并在训练脚本中调用性能调试接口,接着运行训练脚本。启动MindInsight,并通过启动参数指定summary-base-dir目录(summary-base-dir是Profiler所创建目录的父目录),例如训练时Profiler创建的文件夹绝对路径为/home/user/code/data,则summary-base-dir设为/home/user/code。启动成功后,根据IP和端口访问可视化界面在训练列表找到对应训练,点击性能分析,即可在页面中查看训练性能数据首先准备脚本:set_context之后,初始化网络、以及初始化HCCL之前,需要初始化MindSpore Profiler对象。调用Profiler.analyse()停止性能数据收集并生成性能分析结果。Profiler可以通过start_profile参数控制是否基于step(epoch)开启、关闭收集性能数据。对于图模式的数据下沉模式,只有在每个epoch结束后才有机会告知CANN开启和停止,因此对于数据下沉模式,需要基于epoch开启和关闭。正常正常情况下,代码如下:import numpy as npfrom mindspore import nn, contextfrom mindspore import Modelimport mindspore.dataset as dsfrom mindspore.profiler import Profilerclass Net(nn.Cell):    def __init__(self):        super(Net, self).__init__()        self.fc = nn.Dense(2, 2)    def construct(self, x):        return self.fc(x)def generator():    for i in range(2):        yield (np.ones([2, 2]).astype(np.float32), np.ones([2]).astype(np.int32))def train(net):    optimizer = nn.Momentum(net.trainable_params(), 1, 0.9)    loss = nn.SoftmaxCrossEntropyWithLogits(sparse=True)    data = ds.GeneratorDataset(generator, ["data", "label"])    model = Model(net, loss, optimizer)    model.train(1, data)if __name__ == '__main__':    context.set_context(mode=context.GRAPH_MODE, device_target="Ascend")    # Init Profiler    # Note that the Profiler should be initialized after context.set_context and before model.train    # If you are running in parallel mode on Ascend, the Profiler should be initialized before HCCL    # initialized.    profiler = Profiler(output_path = './profiler_data')    # Train Model    net = Net()    train(net)    # Profiler end    profiler.analyse()图模式:对于非数据下沉,需要基于step开启代码如下:from mindspore.profiler.callback import Callbackclass StopAtStep(Callback):    def __init__(self, start_step, stop_step):        super(StopAtStep, self).__init__()        self.start_step = start_step        self.stop_step = stop_step        self.profiler = Profiler(start_profile=False)    def step_begin(self, run_context):        cb_params = run_context.original_args()        step_num = cb_params.cur_step_num        if step_num == self.start_step:            self.profiler.start()    def step_end(self, run_context):        cb_params = run_context.original_args()        step_num = cb_params.cur_step_num        if step_num == self.stop_step:            self.profiler.stop()    def end(self, run_context):        self.profiler.analyse()对于数据下沉,需要基于epoch开启class StopAtEpoch(Callback):    def init(self, start_epoch, stop_epoch):        super(StopAtStep, self).init()        self.start_epoch = start_epoch        self.stop_epoch = stop_epoch        self.profiler = Profiler(start_profile=False)    def epoch_begin(self, run_context):        cb_params = run_context.original_args()        epoch_num = cb_params.cur_epoch_num        if step_num == self.start_epoch:          self.profiler.start()    def epoch_end(self, run_context):        cb_params = run_context.original_args()        epoch_num = cb_params.cur_epoch_num        if epoch_num == self.stop_epoch:            self.profiler.stop()    def end(self, run_context):        self.profiler.analyse() 
  • [活动体验] MindSpore应用梯度累积算法
    概述本教程介绍梯度累积的训练方式,目的是为了解决由于内存不足导致某些大型网络无法训练大Batch_size的问题。传统的训练方式是每次计算得到loss和梯度后,直接用所得梯度对参数进行更新。与传统的训练方式不同,梯度累积引入Mini-batch的概念,首先对每个Mini-batch的数据计算loss和梯度,但不立即更新模型参数,而是先对所得梯度进行累加,然后在指定数量(N)个Mini-batch之后,用累积后的梯度更新网络参数。下次训练前清空过往累积梯度后重新累加,如此往复。最终目的是为了达到跟直接用N*Mini-batch数据训练几乎同样的效果。本篇教程将分别介绍在单机模式和并行模式下如何实现梯度累积训练。梯度下降算法大致分为三种1. 批量梯度下降(Batch Gradient Descent,BGD)2. 批量梯度下降(Batch Gradient Descent,BGD)3. 小批量梯度下降(Mini-Batch Gradient Descent,MBGD)单机模式在单机模式下,主要通过将训练流程拆分为正向反向训练、参数更新和累积梯度清理三个部分实现梯度累积。这里以MNIST作为示范数据集,自定义简单模型实现梯度累积需要如下几个步骤。首先要导入需要的文库:代码如下:import argparseimport osfrom collections.abc import Iterableimport mindspore.nn as nnfrom mindspore import ParameterTuplefrom mindspore import context, DatasetHelper, save_checkpointfrom mindspore.nn import Cellimport mindspore.ops as opsfrom model_zoo.official.cv.lenet.src.dataset import create_datasetfrom model_zoo.official.cv.lenet.src.lenet import LeNet5加载数据集:利用MindSpore的dataset提供的MnistDataset接口加载MNIST数据集,此部分代码由model_zoo中lenet目录下的dataset.py导入。得到定义训练模型:通过mini_steps控制每次更新参数前的累加次数。达到累加次数后进行参数更新和 累加梯度变量清零。代码如下:class GradientAccumulation:    def __init__(self, network, loss_fn, optimizer):        self._network = network        self._loss_fn = loss_fn        self._optimizer = optimizer        params = self._optimizer.parameters        self._grad_sum = params.clone(prefix="grad_sum", init='zeros')        self._zeros = params.clone(prefix="zeros", init='zeros')        self._train_forward_backward = self._build_train_forward_backward_network()        self._train_optim = self._build_train_optim()        self._train_clear = self._build_train_clear()    @staticmethod    def _transform_callbacks(callbacks):        """Transform callback to a list."""        if callbacks is None:            return []        if isinstance(callbacks, Iterable):            return list(callbacks)        return [callbacks]    def _build_train_forward_backward_network(self):        """Build forward and backward network"""        network = self._network        network = nn.WithLossCell(network, self._loss_fn)        loss_scale = 1.0        network = TrainForwardBackward(network, self._optimizer, self._grad_sum, loss_scale).set_train()        return network    def _build_train_optim(self):        """Build optimizer network"""        network = TrainOptim(self._optimizer, self._grad_sum).set_train()        return network    def _build_train_clear(self):        """Build clear network"""        network = TrainClear(self._grad_sum, self._zeros).set_train()        return network    def train_process(self, epoch, train_dataset, mini_steps=None):        """        Training process. The data would be passed to network directly.        """        dataset_helper = DatasetHelper(train_dataset, dataset_sink_mode=False, epoch_num=epoch)        for i in range(epoch):            step = 0            for k, next_element in enumerate(dataset_helper):                loss = self._train_forward_backward(*next_element)                if (k + 1) % mini_steps == 0:                    step += 1                    print("epoch:", i + 1, "step:", step, "loss is ", loss)                    self._train_optim()                    self._train_clear()            train_dataset.reset()        save_checkpoint(self._train_forward_backward, "gradient_accumulation.ckpt", )训练并保存模型调用网络、优化器及损失函数,然后自定义GradientAccumulation的train_process接口,进行模型训练if __name__ == "__main__":    parser = argparse.ArgumentParser(description='MindSpore Grad Cumulative Example')    parser.add_argument('--device_target', type=str, default="GPU", choices=['GPU'],                        help='device where the code will be implemented (default: GPU)')    parser.add_argument('--data_path', type=str, default="./Data",                        help='path where the dataset is saved')    args = parser.parse_args()    context.set_context(mode=context.GRAPH_MODE, device_target=args.device_target)    ds_train = create_dataset(os.path.join(args.data_path, "train"), 32)    net = LeNet5(10)    net_loss = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction="mean")    net_opt = nn.Momentum(net.trainable_params(), 0.01, 0.9)    model = GradientAccumulation(net, net_loss, net_opt)    print("============== Starting Training ==============")    model.train_process(10, ds_train, mini_steps=4)。 
  • [活动体验] MindSpore——模型溯源
    模型溯源、数据溯源和对比看板同训练看板一样属于可视化组件中的重要组成部分,在对训练数据的可视化中,通过对比看板观察不同标量趋势图发现问题,再使用溯源功能定位问题原因,给于我们在数据增强和深度神经网络中提供高效调优的能力。我们从对比分析进入溯源和对比看板。模型溯源模型溯源可视化用于展示所有训练的模型参数信息 选择上图的模型参数 得出如上图一般的溯源功能区。展示的模型溯源功能区,图像化展示了模型的参数信息。用户可以通过选择列的特定区域,展示区域范围内的模型信息。 如上图所示模型列表》展示所有模型信息,用户可以按指定列进行升序或降序展示模型信息。左侧概览页展示优化目标和相关参数的信息 得到如上图概览页,展示的是优化目标分布、参数重要性和散点图。用户可以选择优化目标来查看参数重要性,再通过点击柱状图来查看参数和优化目标的散点图。对比看板对比看板可视用于多个训练之间的标量曲线对比。 标量对比曲线图展示了多个训练之间的标量曲线对比效果,横坐标是训练步骤,纵坐标是标量值。图中右上角有几个按钮功能,从左到右功能分别是全屏展示,切换Y轴比例,开启/关闭框选,分步回退和还原图形。全屏展示即全屏展示该标量曲线,再点击一次即可恢复。切换Y轴比例是指可以将Y轴坐标进行对数转换。开启/关闭框选是指可以框选图中部分区域,并放大查看该区域, 可以在已放大的图形上叠加框选。分步回退是指对同一个区域连续框选并放大查看时,可以逐步撤销操作。
  • [活动体验] MindSpore——张量可视设计
    特性背景张量可视能够帮助用户直观查看训练过程中的Tensor值,既支持以直方图的形式呈现Tensor的变化趋势,也支持查看某次step的具体Tensor值。Tensor包括权重值、梯度值、激活值等。总体设计Tensor可视主要是解析由MindSpore的TensorSummary算子记录的Tensor数据生成的Summary文件,并把结果返回给前端展示。MindInsight解析时会遵循proto文件(Google Protocol Buffer,是一种高效便捷的结构化数据存储方式)来解析Tensor数据,然后把数据缓存起来,在前端查询特定数据时将其返回供前端展示。Tensor可视支持1-N维的Tensor以表格或直方图的形式展示,对于0维的Tensor,需要通过ScalarSummary来记录并在标量可视中展示。在表格视图中,可以查询当前缓存中特定step的Tensor数据,后台通过切片操作使得用户单次可以查询任意0-2维的Tensor数据。在直方图视图中,可以查询当前缓存中所有step的直方图数据。后端设计张量可视相关的类主要有TensorContainer、Histogram以及TensorProcessor类,其中TensorContainer用于保存Tensor的具体值、维度、数据类型、最大值、最小值、直方图等信息,这里的直方图引用了Histogram的数据。Histogram用于处理直方图相关的信息,包括保存桶个数,归一化缓存中所有step的直方图数据等。TensorProcessor用于处理与Tensor相关的HTTP请求,包括获取当前缓存中特定训练作业,特定tag有多少个step,每个step的Tensor统计信息,特定step的特定维度的Tensor数据(单次支持查询最多某两维的数据)以及特定tag的直方图数据。前端设计 将我们所记录的张量以表格的形式展示,包含以下功能:表格中白色方框显示当前展示的是哪个维度下的张量数据,其中冒号:表示当前维度索引范围,和Python索引含义基本一致,不指定具体索引表示当前维度所有值,2:5表示索引2到5(不包括5)的值,可以在方框输入对应的索引或者含有:的索引范围来查询特定维度的张量数据。拖拽表格下方的空心圆圈可以查询特定步骤的张量数据。 接口设计在张量可视中,主要有文件接口和RESTful API接口,其中文件接口为summary.proto文件,是MindInsight和MindSpore进行数据对接的接口。 RESTful API接口是MindInsight前后端进行数据交互的接口,是内部接口。文件接口设计summary.proto文件为总入口,其中张量的数据(TensorProto)存放在Summary的Value中代码如下:{    message Summary {        message Image {            // Dimensions of the image.            required int32 height = 1;            required int32 width = 2;            ...        }        message Histogram {          message bucket{              // Counting number of values fallen in [left, left + width).              // For the rightmost bucket, the range is [left, left + width].              required double left = 1;              required double width = 2;              required int64 count = 3;          }          repeated bucket buckets = 1;          ...        }        message Value {            // Tag name for the data.            required string tag = 1;            // Value associated with the tag.            oneof value {                float scalar_value = 3;                Image image = 4;                TensorProto tensor = 8;                Histogram histogram = 9;            }        }    // Set of values for the summary.    repeated Value value = 1;} 
  • [活动体验] MindSpore——可视总体设计
    训练可视功能主要包括训练看板、模型溯源、数据溯源等功能,训练看板中又包括标量、参数分布图、计算图、数据图、数据抽样、张量等子功能。本文主要介绍MindInsight训练可视功能的逻辑架构、代码组织和数据模型。训练可视逻辑架构 在架构上,训练可视功能的逻辑架构分为两部分:训练信息收集架构,训练信息分析及展示架构。如下图:信息收集架构信息收集功能在MindSpore中,包括训练信息收集API模块和训练信息持久化模块。信息收集API包括:基于summary算子的训练信息收集API。这部分API主要包括4个summary算子,即用于记录标量数据的ScalarSummary算子,用于记录图片数据的ImageSummary算子,用于记录参数分布图(直方图)数据的HistogramSummary算子和用于记录张量数据的TensorSummary算子。请访问算子支持列表以获取关于这些算子的信息。基于Python API的信息收集API。通过SummaryRecord.add_value方法,可以在Python代码中完成信息的收集。易用的信息收集callback。通过SummaryCollector这一callback可以方便地收集常用训练信息到训练日志中。信息持久化模块主要包括用于管理缓存的summary_record模块和用于并行处理数据、写入文件的write_pool模块。信息持久化后,存储在日志文件(summary文件中)。 训练信息分析及展示架构训练信息分析及展示架构在MindInsight中,包括Web UI和后端两大部分。后端从下到上可以分为数据加载及缓存层、业务逻辑层、API 层。数据加载及缓存层主要由训练日志文件发现模块、训练日志文件解析模块及缓存管理模块组成。业务逻辑层主要由训练看板业务模块和溯源业务模块组成。API层主要由RESTful API模块组成。各模块的主要功能如下:训练日志文件发现模块:用于在给定的训练日志根目录(summary-base-dir)中扫描并发现含有训练日志文件的训练日志目录。只有含有训练日志文件的目录会被识别为训练日志目录。训练日志文件解析模块:用于解析训练日志文件。缓存管理模块:用于管理训练日志解析任务,缓存训练日志解析结果。其会定期调用训练日志发现模块,扫描最新的训练日志目录列表;然后调用解析模块解析文件内容,将解析结果存储在缓存中以供UI查询。训练看板模块:用于提供训练看板功能的业务逻辑,支撑UI查询训练看板数据。溯源模块:用于提供模型溯源和数据溯源的业务逻辑,支撑UI查询溯源数据。RESTful API模块:用于将业务模块提供的接口包装为RESTful API训练可视数据模型训练信息数据流训练信息产生于用户训练的过程中。用户可以通过训练信息收集API将这些训练信息收集起来,并通过训练信息持久化模块将这些训练信息保存到磁盘上,产生训练日志文件(summary文件)。训练日志文件生成后,便可以使用MindInsight对其中的信息进行可视化。  数据模型MindInsight的简要数据模型如图3所示。一个训练日志目录会被MindInsight识别为一个训练作业。训练作业是MindInsight的最小管理单元。一个训练作业可以关联0-1个溯源数据,关联0-1个训练过程数据。训练过程数据内部有着丰富的结构,每一个具体的数据,可以通过给定的插件名称、标签和迭代唯一确定。下面将分别介绍这些概念。 
  • [活动体验] MindSpore——异构并行
    概述异构并行训练方法是通过分析图上算子内存占用和计算密集度,将内存消耗巨大或适合CPU逻辑处理的算子切分到CPU子图,将内存消耗较小计算密集型算子切分到硬件加速器子图,框架协同不同子图进行网络训练,使得处于不同硬件且无依赖关系的子图能够并行进行执行的过程。计算流程MindSpore异构并行训练典型的计算流程如下图所示: 用户设置网络执行的后端代码如下:from mindspore import contextcontext.set_context(device_target="Ascend");用户设置特定算子执行后端代码如下:prim.add_prim_attr("primitive_target", "CPU");框架根据计算图算子标志进行切图框架调度不同后端执行子图优化器异构在盘古或GPT3大模型训练过程中,优化器状态占用了大量内存,进而限制了可训练的模型规模。使用优化器异构,将优化器指定到CPU上执行,可以极大扩展可训练模型规模:如图: 如图所示,将Adam算子配置到CPU执行同时指定加速器进行FP16计算,可以将参数内存占用降低到原始的1/3。配置优化器算子到CPU执行初始化FP16的权重参数以及FP32的优化器状态变量将输入优化器的梯度转为FP16(如果本来就是FP16梯度,可忽略这步)权重和梯度转为FP32参与优化器运更新后的FP32权重赋值给FP16的权重代码如下:import numpy as npfrom mindspore import dtype as mstypeimport mindspore.ops as opsfrom mindspore.common.initializer import initializerfrom mindspore import Tensorfrom mindspore import ParameterTuplefrom mindspore.nn import Optimizer_adam_opt = ops.MultitypeFuncGraph("adam_opt")host_assign = ops.Assign()host_assign.add_prim_attr("primitive_target", "CPU")host_cast = ops.Cast()host_cast.add_prim_attr("primitive_target", "CPU")device_cast = ops.Cast()@_adam_opt.register("Function", "Tensor", "Tensor", "Tensor", "Tensor", "Number", "Tensor", "Tensor", "Tensor",                    "Tensor", "Bool", "Bool")def _update_run_kernel(opt, beta1, beta2, eps, lr, weight_decay, param, m, v, gradient, decay_flags, optim_filter):  """  Update parameters by AdamWeightDecay op.  """  success = True  if optim_filter:    param32 = host_cast(param, mstype.float32)    gradient = device_cast(gradient, mstype.float32)    if decay_flags:      next_param = opt(param32, m, v, lr, beta1, beta2, eps, weight_decay, gradient)    else:      next_param = opt(param32, m, v, lr, beta1, beta2, eps, 0.0, gradient)    ret = host_assign(param, host_cast(ops.depend(param32, next_param), ops.dtype(param)))    return ops.depend(success, ret)  return successclass AdamWeightDecayOp(Optimizer):  def __init__(self, params, learning_rate=1e-3, beta1=0.9, beta2=0.999, eps=1e-6, weight_decay=0.0):    super(AdamWeightDecayOp, self).__init__(learning_rate, params, weight_decay)    self.beta1 = Tensor(np.array([beta1]).astype(np.float32))    self.beta2 = Tensor(np.array([beta2]).astype(np.float32))    self.eps = Tensor(np.array([eps]).astype(np.float32))    self.moments1 = self.clone_param32(prefix="adam_m", init='zeros')    self.moments2 = self.clone_param32(prefix="adam_v", init='zeros')    self.opt = ops.AdamWeightDecay()    self.hyper_map = ops.HyperMap()    self.opt.add_prim_attr("primitive_target", "CPU")  def construct(self, gradients):    """AdamWeightDecayOp"""    lr = self.get_lr()    if self.is_group:      if self.is_group_lr:        optim_result = self.map_reverse(ops.partial(_adam_opt, self.opt, self.beta1, self.beta2, self.eps),                                        lr, self.weight_decay, self.parameters, self.moments1, self.moments2,                                        gradients, self.decay_flags, self.optim_filter)      else:        optim_result = self.map_reverse(ops.partial(_adam_opt, self.opt, self.beta1, self.beta2, self.eps, lr),                                        self.weight_decay, self.parameters, self.moments1, self.moments2,                                        gradients, self.decay_flags, self.optim_filter)    else:      optim_result = self.map_reverse(ops.partial(_adam_opt, self.opt, self.beta1, self.beta2, self.eps, lr,                                                self.weight_decay), self.parameters, self.moments1, self.moments2,                                      gradients, self.decay_flags, self.optim_filter)    return optim_result  def clone_param32(self, prefix, init=None):    new = []    for old_param in self.parameters:      param_init = init      if init is None:        param_init = old_param.init      new_state = old_param.clone()      new_state.set_dtype(mstype.float32)      new_state.set_data(initializer(param_init, shape=old_param.shape, dtype=mstype.float32))      new_state.name = prefix + '.' + new_state.name      new.append(new_state)    return ParameterTuple(new)Embedding异构在一些需要查Embedding大表的网络中,Embedding表往往有上百G的规模,受加速器内存大小限制,无法直接将整表加载到加速器上执行。通过将与权重表相连的算子放到CPU上执行,避免加速器由于内存限制而无法训练网络的问题。如图: 配置EmbeddingLookup算子到CPU执行代码如下:|ops.EmbeddingLookup().add_prim_attr('primitive_target', 'CPU');配置EmbeddingLookup相关优化器到CPU执行代码如下:import mindspore.nn as nnimport mindspore.ops as opsfrom mindspore import Parameterfrom mindspore.common.initializer import initializerclass EmbeddingLookup(nn.Cell):  def __init__(self, vocab_size, embedding_size, param_init='normal',               target='CPU', sparse=True):    """Initialize EmbeddingLookup."""    super(EmbeddingLookup, self).__init__()    validator.check_value_type('sparse', sparse, [bool], self.cls_name)    self.vocab_size = validator.check_positive_int(vocab_size, 'vocab_size')    self.target = target    self.sparse = sparse    if target not in ('CPU', 'DEVICE'):      raise ValueError('Attr \'target\' of \'EmbeddingLookup\' Op passed '                       + str(target) + ', should be one of values in \'CPU\', \'DEVICE\'.')    if not sparse and target == 'CPU':      raise ValueError('When target is CPU, embedding_lookup must be sparse.')    if sparse:      self.gatherv2 = ops.SparseGatherV2()    else:      self.gatherv2 = ops.Gather()    self.embeddinglookup = ops.EmbeddingLookup().add_prim_attr('primitive_target', 'CPU')    self.embedding_size = validator.check_positive_int(embedding_size, 'embedding_size')    self.embedding_table = Parameter(initializer(param_init, [self.vocab_size, self.embedding_size]),                                     name='embedding_table')  def construct(self, indices):    if self.target == "CPU":      out = self.embeddinglookup(self.embedding_table, indices, 0)    else:      out = self.gatherv2(self.embedding_table, indices, 0)    return outPS异构在EmbeddingTable达到T级别,单机内存无法放下时,使用Parameter Server,通过异构的Pull/Push算子进行权重的拉取和更新。如下图:  
  • [活动体验] MindSpore使用Delegate支持第三方AI框架接入
    第三方框架可以是用户自己实现,也可以是业内其他开源的框架,一般都具备在线构图的能力,即可以将多个算子构建成一张子图发放给设备执行。新增自定义Delegate类自定义Delegate要继承自deleate类。可以在构造函数中完成对第三方框架调度硬件设备有关config的初始化,如NPU指定频率、CPU指定线程数等。代码如下:class XXXDelegate : public Delegate { public:  XXXDelegate() = default;  ~XXXDelegate() = default;  Status Init() = 0;  Status Build(DelegateModel *model) = 0;}在完成上面后,我们要初始化接口,代码如下:Status XXXDelegate::Init() {  // 1. Check whether the inference device matches the delegate framework.  // 2. Initialize delegate related resources.}实现构图接口Build会在Model的Build接口被调用。具体的位置在MindSpore Lite内部代码Schedule::Schedule函数中,此时已完成内置算子选择,算子存放在DelegateModel的Kernel列表中。Build需要实现以下功能:遍历Kernel列表,调用GetPrimitive获取每个算子对应的属性值,解析该算子的属性值,判断Delegate框架是否支持。对连续可支持的一段算子列表,构建一张Delegate子图,调用Replace用子图Kernel去替换这段连续的算子。代码如下:Status XXXDelegate::Build(DelegateModel *model) {  KernelIter from = model->BeginKernelIterator();                   // Record the start operator position supported by the Delegate  KernelIter end = model->BeginKernelIterator();                    // Record the end operator position supported by the Delegate  for (KernelIter iter = model->BeginKernelIterator(); iter != model->EndKernelIterator(); iter++) {    kernel::Kernel *kernel = *iter;    if (IsSupport(kernel, model->GetPrimitive(kernel))) {           // Check whether the Delegate framework supports the kernel according to the primitive      end = iter;    } else {                                                        // The current kernel is not supported, and the sub-graph is truncated      if (from != end) {        auto xxx_graph_kernel = CreateXXXGraph(from, end, model);   // Create a Delegate sub-graph Kernel        iter = model->Replace(from, end + 1, xxx_graph_kernel);     // Replace the supported kernels list with a Delegate sub-graph Kernel      }      from = iter + 1;      end = iter + 1;    }  }  return RET_OK;}实现子图Kernel上述CreateXXXGraph接口要返回一张Delegate的子图,代码如下所示:kernel::Kernel *XXXDelegate::CreateXXXGraph(KernelIter from, KernelIter end, DelegateModel *model) {  auto in_tensors = GraphInTensors(...);    // Find the input tensors of the Delegate sub-graph  auto out_tensors = GraphOutTensors(...);  // Find the output tensors of the Delegate sub-graph  auto graph_kernel = new (std::nothrow) XXXGraph(in_tensors, out_tensors);  if (graph_kernel == nullptr) {    MS_LOG(ERROR) << "New XXX Graph failed.";    return nullptr;  }  // Build graph online, load model, etc.  return graph_kernel;}子图要定义继承:要注意的是:根据原始的Kernel列表找到正确的in_tensors和out_tensors,以便Execute时,能找到正确的输入tensor和输入数据,并将输出数据写回到正确的地址中。代码如下:class XXXGraph : public kernel::Kernel { public:  XXXGraph(const std::vector<tensor::MSTensor *> &inputs, const std::vector<tensor::MSTensor *> &outputs)      : kernel::Kernel(inputs, outputs, nullptr, nullptr) {}  ~XXXGraph() override;  int Prepare() override {    // Generally, the model will be built only once, so Prepare is also called once.    // Do something without input data, such as pack the constant weight tensor, etc.  }  int Execute() override {    // Obtain input data from in_tensors.    // Execute the inference process.    // Write the result back to out_tensors.  }  int ReSize() override {    // Support dynamic shape, and input shape will changed.  }};Lite框架调度Lite框架要调度用户自定义的Delegate,在创建Context时,需要通过setdelegate设置自定义Delegate指针,见以下示例代码。再通过build传递给Lite框架。如果Context中的Delegate为空指针,推理流程会调用到Lite框架内置的推理。代码如下:auto context = std::make_shared<mindspore::Context>();if (context == nullptr) {  MS_LOG(ERROR) << "New context failed";  return RET_ERROR;}auto delegate = std::make_shared<XXXDelegate>();if (delegate == nullptr) {  MS_LOG(ERROR) << "New XXX delegate failed";  return RET_ERROR;}context->SetDelegate(delegate);auto model = new (std::nothrow) mindspore::Model();if (model == nullptr) {  std::cerr << "New Model failed." << std::endl;}// Assuming that we have read a ms file and stored in the address pointed by model_bufauto build_ret = model->Build(model_buf, size, mindspore::kMindIR, context);delete[](model_buf);if (build_ret != mindspore::kSuccess) {  std::cerr << "Build model failed." << std::endl;} 实现Build接口Build接口解析DelegateModel实例,主要实现算子支持判断、子图构建、在线构图等功能。代码如下:Status NPUDelegate::Build(DelegateModel *model) {  KernelIter from, end;                     // Record the start and end positions of kernel supported by the NPU sub-graph.  std::vector<NPUOp *> npu_ops;             // Save all NPUOp used to construct an NPU sub-graph.  int graph_index = 0;  for (KernelIter iter = model->BeginKernelIterator(); iter != model->EndKernelIterator(); iter++) {    kernel::Kernel *kernel = *iter;    auto npu_op = GetOP(kernel, model->GetPrimitive(kernel));  // Obtain an NPUOp according to the kernel and the primitive. Each NPUOp contains information such as input tensors, output tensors and operator attribute.    if (npu_op != nullptr) {                // NPU supports the current kernel.      if (npu_ops.size() == 0) {        from = iter;      }      npu_ops.push_back(npu_op);      end = iter;    } else {                                 // NPU does not support the current kernel.      if (npu_ops.size() > 0) {        auto npu_graph_kernel = CreateNPUGraph(npu_ops);  // Create a NPU sub-graph kernel.        if (npu_graph_kernel == nullptr) {          MS_LOG(ERROR) << "Create NPU Graph failed.";          return RET_ERROR;        }        npu_graph_kernel->set_name("NpuGraph" + std::to_string(graph_index++));        iter = model->Replace(from, end + 1, npu_graph_kernel);  // Replace the supported kernel list with a NPU sub-graph kernel.        npu_ops.clear();      }    }  }  auto ret = npu_manager_->LoadOMModel();    // Build model online. Load NPU model.  if (ret != RET_OK) {    MS_LOG(ERROR) << "NPU client load model failed.";    return RET_ERROR;  }  return RET_OK;}实现构图代码用于生成一张NPU子图。代码如下:kernel::Kernel *NPUDelegate::CreateNPUGraph(const std::vector<NPUOp *> &ops) {  auto in_tensors = GraphInTensors(ops);  auto out_tensors = GraphOutTensors(ops);  auto graph_kernel = new (std::nothrow) NPUGraph(ops, npu_manager_, in_tensors, out_tensors);  if (graph_kernel == nullptr) {    MS_LOG(DEBUG) << "New NPU Graph failed.";    return nullptr;  }  ret = graph_kernel->Init();  if (ret != RET_OK) {    MS_LOG(DEBUG) << "NPU Graph Init failed.";    return nullptr;  }  return graph_kernel;} 
  • [活动体验] 基于MindSpore Serving部署推理
    MindSpore Serving是一个轻量级、高性能的推理服务模块,旨在帮助MindSpore开发者在生产环境中高效部署在线推理服务。当用户使用MindSpore完成模型训练后,导出MindSpore模型,即可使用MindSpore Serving创建该模型的推理服务。MindSpore Serving提供如下功能:加载模型文件生成推理引擎,提供推理功能;预测请求和处理结果的消息交互,支持gPRC和RESTful两种请求方式;预测接口调用,执行预测,返回预测结果;模型的生命周期管理;服务的生命周期管理;多模型多版本的管理。有点也是显而易见,所要学习也是很有必要的:启动时需要如下文件:tensor_add├── add/│    └── servable_config.py│    └── 1/│        └── tensor_add.mindir└── serving_server.pytensor_add.mindir为模型文件,放置在文件夹1下,1为版本号。不同的版本放置在不同的文件夹下,版本号需以纯数字串命名,默认配置下启动最大数值的版本号的模型文件。servable_config.py为模型配置文件,定义了模型的处理函数,包括add_common和add_cast两个方法,add_common定义了输入为两个普通float32类型的加法操作,add_cast定义输入类型为其他类型,经过输入类型转换float32后的加法操作。配置模型代码如下:import numpy as npfrom mindspore_serving.server import registerdef add_trans_datatype(x1, x2):    """预处理定义,本例中有两个输入和输出"""    return x1.astype(np.float32), x2.astype(np.float32)# 进行模型声明,其中declare_model入参model_file指示模型的文件名称,model_format指示模型的模型类别# 当with_batch_dim设定为False时, 仅支持2x2的Tensor# 当with_batch_dim设定为True时, 可支持Nx2的Tensor, N的值由batch决定model = register.declare_model(model_file="tensor_add.mindir", model_format="MindIR", with_batch_dim=False)# add_common方法定义# Servable方法的入参由Python方法的入参指定,Servable方法的出参由register_method的output_names指定@register.register_method(output_names=["y"])def add_common(x1, x2):  # 仅支持float32类型的输入    """add_common数据流定义,只调用模型推理"""    y = register.add_stage(model, x1, x2, outputs_count=1)    return y# add_cast方法定义@register.register_method(output_names=["y"])def add_cast(x1, x2):    """add_cast数据流定义,调用预处理和模型推理"""    x1, x2 = register.add_stage(add_trans_datatype, x1, x2, outputs_count=2)  # 将输入转换为 float32    y = register.add_stage(model, x1, x2, outputs_count=1)    return y推理过程一种是通过gRPC方式,一种是通过RESTful方式。本文以gRPC方式为例,通过client.py执行推理。代码如下:import numpy as npfrom mindspore_serving.client import Clientdef run_add_common():    """调用add add_common"""    client = Client("localhost", 5500, "add", "add_common")    instances = []    # 例1    x1 = np.asarray([[1, 1], [1, 1]]).astype(np.float32)    x2 = np.asarray([[1, 1], [1, 1]]).astype(np.float32)    instances.append({"x1": x1, "x2": x2})    # 例2    x1 = np.asarray([[2, 2], [2, 2]]).astype(np.float32)    x2 = np.asarray([[2, 2], [2, 2]]).astype(np.float32)    instances.append({"x1": x1, "x2": x2})    # 例3    x1 = np.asarray([[3, 3], [3, 3]]).astype(np.float32)    x2 = np.asarray([[3, 3], [3, 3]]).astype(np.float32)    instances.append({"x1": x1, "x2": x2})    result = client.infer(instances)    print(result)def run_add_cast():    client = Client("localhost", 5500, "add", "add_cast")    instances = []    x1 = np.ones((2, 2), np.int32)    x2 = np.ones((2, 2), np.int32)    instances.append({"x1": x1, "x2": x2})    result = client.infer(instances)    print(result)if __name__ == '__main__':    run_add_common()    run_add_cast()使用mindspore_serving.client定义的Client类,客户端定义两个用例,分别调用模型的两个方法,run_add_common用例为三对float32类型数组相加操作,run_add_cast用例计算两个int32数组相加操作。执行后显示如下返回值,三对float32类型相加结果合集和一对int32类型的相加结果,说明Serving服务已正确执行Add网络的推理代码如下:[{'y': array([[2. , 2.],        [2.,  2.]], dtype=float32)},{'y': array([[4. , 4.],        [4.,  4.]], dtype=float32)},{'y': array([[6. , 6.],        [6.,  6.]], dtype=float32)}][{'y': array([[2. , 2.],        [2.,  2.]], dtype=float32)}]