十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

昇思MindSpore全场景AI开发实战:从环境搭建到端侧部署的成长指南

昇思MindSpore全场景AI开发实战:从环境搭建到端侧部署的成长指南 1. 从“成长邀请”说起昇思MindSpore到底在邀请开发者做什么第一次看到“致AI开发者昇思MindSpore发来‘成长’邀请”这个标题我脑子里冒出来的第一个念头是又一个框架在喊人入伙。但仔细琢磨了一下“成长”这两个字我觉得它其实点出了一个很现实的问题——AI开发者到底该怎么选框架、怎么学框架、怎么把框架用进自己的项目里而不是停留在“跑通一个MNIST就完事”的阶段。昇思MindSpore是华为开源出来的一个深度学习框架全场景覆盖是它最核心的定位。什么叫全场景简单说就是同一套代码既能在云端的大算力集群上跑训练也能在手机、手表、摄像头这类端侧设备上跑推理。这个特性对开发者来说意味着什么意味着你不需要为“训练”和“部署”维护两套完全不同的代码逻辑省掉了很多重复劳动。我见过太多团队训练用PyTorch部署转ONNX再转各种端侧格式中间踩的坑能写一本书。MindSpore想解决的就是这类割裂问题。那这个“成长邀请”适合谁我的判断是三类人第一类是想入门AI但被各种框架配置劝退的新手MindSpore的API设计相对统一入门曲线没那么陡第二类是有PyTorch或TensorFlow经验、想拓展技术栈的中级开发者尤其是做端侧AI落地的第三类是做科研或者课程项目、需要一套能快速验证想法的工具链的学生和研究者。这篇文章我就围绕“怎么上手、怎么用顺、怎么避坑”这条线把MindSpore从安装到实战的完整路径拆开讲一遍。2. 环境搭建与工具链选型别在第一步就卡住2.1 安装方式的选择逻辑与实操MindSpore的安装方式主要有三种pip安装、conda安装、源码编译。对绝大多数人来说pip就够了源码编译是给需要改框架底层或者用特殊硬件的人准备的。我建议新手直接从pip开始别一上来就折腾源码编译那个时间成本足够你把官方教程跑完两遍了。pip安装的核心是选对版本组合。MindSpore的版本和Python版本、CUDA版本之间有对应关系选错了就是各种import报错。截至我写这篇文章时的经验比较稳的组合是Python 3.9加上MindSpore 2.x系列。安装命令大致长这样pip install mindspore如果你有NVIDIA显卡想做GPU训练那就换成对应的GPU版本包。这里有个坑要提醒不要盲目装最新版先去看官方文档的版本配套表确认你的CUDA版本在支持列表里。我有一次图省事直接装了最新版结果CUDA版本不匹配折腾了一个下午才定位到问题。安装完之后用下面这段代码验证是否成功import mindspore print(mindspore.__version__) print(mindspore.get_context(device_target))如果输出了版本号并且device_target显示为CPU或GPU取决于你的配置那就说明环境没问题了。注意如果你在Windows上安装早期版本对Windows的支持有限建议优先考虑Linux环境或者WSL。我在Windows原生环境下踩过算子不支持的坑换到Linux之后顺畅很多。2.2 VSCode搭配MindSpore内核的配置要点热搜词里出现了“vscode使用mindspore内核”这个点值得单独说一下。很多人习惯用VSCode写Python那怎么让VSCode里的Jupyter Notebook用上MindSpore的内核步骤其实不复杂。首先确保你在目标Python环境里装了ipykernelpip install ipykernel python -m ipykernel install --user --namemindspore_env --display-nameMindSpore然后在VSCode里打开一个.ipynb文件右上角选择内核的时候就能看到名为“MindSpore”的内核选项。选中它之后Notebook里import mindspore就能正常工作了。这里有个细节如果你用的是虚拟环境一定要先激活虚拟环境再执行上面的ipykernel安装命令否则内核会注册到全局Python上VSCode里选内核的时候就会找不到或者版本不对。我自己就犯过这个错注册完发现内核列表里多了一个但import报错后来才发现是注册到了系统Python而不是虚拟环境。另外VSCode的Python插件和Jupyter插件要保持更新老版本插件有时候识别不到自定义内核。这个不算MindSpore的问题但确实会影响体验。2.3 工具链搭配的取舍思路除了VSCodeMindSpore还支持在ModelArts等云端环境里直接开发也支持命令行脚本训练。我的建议是本地开发调试用VSCode加Jupyter Notebook正式训练任务用脚本模式提交到服务器或云端。Notebook适合快速验证想法和调试但它的交互式特性决定了不适合跑长时间的训练任务中间断一次就前功尽弃。脚本模式就是标准的Python脚本用python train.py这种方式启动。MindSpore提供了Model.train()和model.train()两种训练接口前者是高层封装后者更灵活。新手先用高层封装把流程跑通等需要自定义训练逻辑的时候再往下钻。3. 核心概念拆解把MindSpore的“骨架”摸清楚3.1 张量与算子最基础的两块砖任何深度学习框架最底层的两个概念都是张量和算子。张量你可以理解成多维数组和NumPy的ndarray很像但多了自动微分和硬件加速的能力。MindSpore里创建张量的方式和NumPy几乎一样import mindspore as ms from mindspore import Tensor import numpy as np x Tensor(np.array([1, 2, 3]), ms.float32) print(x.shape) # (3,) print(x.dtype) # Float32算子就是各种计算操作加减乘除、矩阵乘法、卷积、激活函数等等。MindSpore的算子接口设计得比较直观比如ms.ops.MatMul、ms.ops.ReLU。和PyTorch不同的是MindSpore有“函数式算子”和“nn层”两套接口前者更底层更灵活后者更适合搭网络。我个人的使用习惯是搭网络结构用nn模块里的层做自定义计算逻辑用ops里的函数式算子。两者可以混用不冲突。3.2 自动微分与计算图MindSpore的“内功”自动微分是深度学习框架的核心能力。MindSpore用的是基于源码转换的自动微分机制简单说就是它能在编译阶段把Python函数转换成计算图然后对计算图做微分。这个机制带来的好处是执行效率高尤其是在Ascend硬件上代价是有些Python的动态特性在计算图模式下不支持。这就引出了一个新手经常踩的坑在construct函数里用了Python的print、if判断列表长度、动态改变张量形状等操作结果报错。解决办法是用MindSpore提供的图模式兼容写法比如用ms.ops.Print代替print用ms.ops.cond代替Python的if。class MyNet(nn.Cell): def construct(self, x): if x.shape[0] 1: # 这种写法在图模式下可能出问题 return x * 2 return x上面这种写法在PyNative模式下没问题但切到Graph模式下就可能报错。稳妥的做法是把条件判断改成算子形式或者确保输入形状是固定的。3.3 PyNative与Graph模式两种执行模式的切换MindSpore有两种执行模式PyNative动态图和Graph静态图。PyNative模式就是逐行执行和PyTorch的动态图体验很像适合调试Graph模式会把整个函数编译成计算图再执行速度快但调试困难。切换方式很简单ms.set_context(modems.PYNATIVE_MODE) # 动态图 ms.set_context(modems.GRAPH_MODE) # 静态图我的经验是开发调试阶段用PyNative确认逻辑没问题之后再切Graph跑正式训练。这样既能享受动态图的调试便利又能拿到静态图的性能收益。但要注意切换模式之后一定要重新跑一遍完整流程因为有些在PyNative下能跑的代码在Graph下会报错提前发现比训练到一半崩掉要好得多。4. 从零搭一个完整训练流程手把手实操4.1 数据集加载与预处理MindSpore的数据集加载接口在mindspore.dataset模块里。以常用的CIFAR-10为例加载和预处理的核心代码如下import mindspore.dataset as ds import mindspore.dataset.vision as vision import mindspore.dataset.transforms as transforms from mindspore import dtype as mstype def create_dataset(data_path, batch_size32, repeat_num1): dataset ds.Cifar10Dataset(data_path, shuffleTrue) resize_op vision.Resize((224, 224)) rescale_op vision.Rescale(1.0 / 255.0, 0.0) normalize_op vision.Normalize( mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225] ) hwc2chw_op vision.HWC2CHW() type_cast_op transforms.TypeCast(mstype.int32) dataset dataset.map(operationsresize_op, input_columnsimage) dataset dataset.map(operationsrescale_op, input_columnsimage) dataset dataset.map(operationsnormalize_op, input_columnsimage) dataset dataset.map(operationshwc2chw_op, input_columnsimage) dataset dataset.map(operationstype_cast_op, input_columnslabel) dataset dataset.batch(batch_size, drop_remainderTrue) dataset dataset.repeat(repeat_num) return dataset这段代码里有几个点值得展开说。Rescale把像素值从0-255缩放到0-1Normalize做标准化这两个是标配操作。HWC2CHW是把通道维度从最后移到最前因为MindSpore的卷积层默认输入格式是NCHW。drop_remainderTrue表示最后一个不满batch的数据丢掉这个在训练时建议开启避免batch size不一致导致的形状报错。提示map操作的顺序有讲究。先做Resize再做Rescale和Normalize顺序反了会导致数值范围不对。这个坑我在早期项目里踩过loss一直不下降排查了半天才发现是预处理顺序的问题。4.2 网络定义用nn.Cell搭积木MindSpore定义网络的方式是继承nn.Cell在__init__里声明层在construct里定义前向计算。下面是一个简化的ResNet风格网络import mindspore.nn as nn import mindspore.ops as ops class ResidualBlock(nn.Cell): def __init__(self, in_channels, out_channels, stride1): super(ResidualBlock, self).__init__() self.conv1 nn.Conv2d(in_channels, out_channels, kernel_size3, stridestride, padding1, pad_modepad) self.bn1 nn.BatchNorm2d(out_channels) self.relu nn.ReLU() self.conv2 nn.Conv2d(out_channels, out_channels, kernel_size3, stride1, padding1, pad_modepad) self.bn2 nn.BatchNorm2d(out_channels) self.downsample nn.SequentialCell() if stride ! 1 or in_channels ! out_channels: self.downsample nn.SequentialCell( nn.Conv2d(in_channels, out_channels, kernel_size1, stridestride), nn.BatchNorm2d(out_channels) ) def construct(self, x): identity self.downsample(x) out self.relu(self.bn1(self.conv1(x))) out self.bn2(self.conv2(out)) out out identity out self.relu(out) return out这里有个细节pad_modepad和padding1配合使用表示在两边各补一圈零。MindSpore的Conv2d默认pad_mode是same会自动计算padding但有时候自动计算的结果和你的预期不一致所以我习惯显式指定。SequentialCell是MindSpore的容器和PyTorch的nn.Sequential类似。注意它叫SequentialCell不是Sequential这个命名差异新手容易搞混。4.3 损失函数、优化器与训练循环损失函数和优化器的用法很直接net ResidualBlock(3, 64) loss_fn nn.SoftmaxCrossEntropyWithLogits(sparseTrue, reductionmean) optimizer nn.Momentum(net.trainable_params(), learning_rate0.01, momentum0.9)sparseTrue表示标签是整数类别而不是one-hot编码这个参数设错了会导致loss计算异常。net.trainable_params()自动收集所有需要训练的参数不用手动指定。训练循环可以用高层封装的Model接口from mindspore import Model from mindspore.train.callback import LossMonitor model Model(net, loss_fnloss_fn, optimizeroptimizer, metrics{accuracy}) model.train(epoch10, train_datasetdataset, callbacks[LossMonitor()])LossMonitor会在每个epoch结束时打印loss值方便观察训练是否正常。如果要加验证集评估可以在Model初始化时传入eval_dataset然后在model.train里设置dataset_sink_mode等参数。4.4 模型保存与加载训练完的模型要保存下来MindSpore提供了checkpoint机制from mindspore.train.callback import ModelCheckpoint, CheckpointConfig config CheckpointConfig(save_checkpoint_steps100, keep_checkpoint_max5) ckpt_callback ModelCheckpoint(prefixresnet, directory./ckpt, configconfig) model.train(epoch10, train_datasetdataset, callbacks[LossMonitor(), ckpt_callback])save_checkpoint_steps100表示每100步保存一次keep_checkpoint_max5表示最多保留5个checkpoint文件超出的会自动删除旧的。这个机制很实用避免磁盘被checkpoint撑爆。加载模型用load_checkpoint和load_param_into_netfrom mindspore import load_checkpoint, load_param_into_net param_dict load_checkpoint(./ckpt/resnet-10_100.ckpt) load_param_into_net(net, param_dict)加载之后就可以用加载了权重的网络做推理或继续训练。5. 常见问题与排查技巧实录5.1 版本兼容性问题速查版本不匹配是MindSpore新手遇到最多的问题我整理了一个速查表报错信息可能原因解决办法ImportError: libcudart.so not foundCUDA版本不匹配检查CUDA版本安装对应MindSpore GPU包RuntimeError: Device target not supported硬件后端配置错误用ms.set_context(device_targetCPU)显式指定ValueError: For Conv2d, the input shape is invalid输入格式不是NCHW检查数据预处理是否加了HWC2CHWTypeError: unsupported operand type张量dtype不一致用Tensor.astype()统一类型这个表里的问题我都实际遇到过尤其是第一个和第三个排查起来最费时间。5.2 图模式下的常见报错与解决Graph模式下最常见的报错是“The if statement is not supported in graph mode”这类。解决办法前面提过用算子替代Python控制流。另一个高频问题是动态shape不支持比如在construct里根据输入改变张量形状。Graph模式要求形状在编译时确定所以要么固定输入形状要么用ms.ops.DynamicShape相关接口。还有一个坑是Python的list操作。在Graph模式下list的append、索引赋值等操作可能不被支持。我的经验是尽量用Tensor操作代替list操作实在需要list就用nn.CellList。5.3 训练不收敛的排查思路训练loss不下降原因可能有很多。我一般按这个顺序排查第一检查数据预处理。Normalize的均值和标准差是否和数据集匹配标签有没有错位我遇到过一次标签和图像不对应的问题原因是自定义数据集加载时文件排序和标签映射搞错了。第二检查学习率。太大导致震荡太小导致下降缓慢。可以先用一个较小的学习率跑几个step看看loss有没有变化。第三检查损失函数。分类任务用SoftmaxCrossEntropy回归任务用MSE用错了loss会一直很高。sparse参数也要确认。第四检查网络初始化。权重初始化方式对训练影响很大MindSpore的层默认有初始化但自定义层可能需要手动指定。5.4 性能调优的几个实用技巧训练速度慢是另一个高频问题。几个我实测有效的优化手段开启dataset_sink_modeTrue这个能让数据加载和计算流水线并行速度提升明显。但要注意开启之后callback里的step计算方式会变LossMonitor打印的频率可能和预期不同。用混合精度训练。MindSpore支持amp_level参数设置amp_levelO2可以自动做混合精度在GPU上通常能提速30%以上精度损失很小。合理设置batch size。batch size太小GPU利用率上不去太大可能爆显存。一般从32或64开始试根据显存占用调整。数据加载的num_parallel_workers参数也要调。默认值可能偏小设成CPU核心数的一半到相等之间比较合适。6. 端侧部署与全场景能力初探6.1 模型导出为MindIR格式MindSpore的全场景能力核心在于MindIR这个中间表示格式。训练好的模型可以导出为MindIR然后在端侧加载推理import mindspore as ms input_tensor ms.Tensor(np.ones([1, 3, 224, 224]), ms.float32) ms.export(net, input_tensor, file_nameresnet_model, file_formatMINDIR)导出的MindIR文件可以在手机、嵌入式设备上通过MindSpore Lite加载。这个流程比“训练框架导出ONNX再转端侧格式”要简洁中间少了一道转换精度损失的风险也小。6.2 端侧推理的注意事项端侧推理和云端训练有几个关键差异。首先是模型大小端侧存储和内存都有限模型需要做量化压缩。MindSpore Lite支持INT8量化能把模型体积压缩到原来的四分之一左右。其次是算子支持。不是所有训练时用的算子端侧都支持导出前要确认目标算子集。我遇到过一次自定义算子在端侧不支持的情况最后改用了标准算子组合来实现同样的功能。最后是输入形状。端侧推理通常要求固定输入形状动态shape支持有限。导出时就要确定好输入尺寸后续推理都按这个尺寸来。7. 我个人的学习路径建议与踩坑心得回顾我从第一次接触MindSpore到能独立跑通完整项目的经历有几个心得值得分享。不要一上来就啃官方文档的全部内容。官方文档很全但信息密度高新手容易迷失。我的建议是先跑通一个官方提供的端到端示例比如图像分类的quick start把流程走通之后再回头理解每个环节的细节。PyNative模式是你的朋友。调试阶段就用PyNative报错信息清晰能逐行排查。等逻辑确认无误再切Graph模式跑正式训练。我早期为了追求性能直接上Graph模式结果一个简单的维度错误排查了两个小时。社区和论坛比搜索引擎好用。MindSpore的中文社区比较活跃很多坑别人已经踩过了。遇到报错先搜社区往往能直接找到答案比漫无目的地搜要高效得多。版本管理要严格。用虚拟环境隔离不同项目的依赖记录好每个项目用的MindSpore版本和Python版本。我吃过一次亏两个项目共用一个环境升级MindSpore之后老项目跑不了了回滚又影响新项目最后只能重建环境。最后说一个实际体会MindSpore的API设计在向PyTorch靠拢有PyTorch基础的人迁移过来成本不高。主要的差异在于执行模式的概念和部分算子的命名习惯。花半天时间把这两块搞清楚后面就顺了。端侧部署这块是MindSpore的差异化优势如果你有模型落地的需求值得花时间深入。
返回列表