华为云用户手册

  • 准备数据 单击下载猫狗数据集至本地,并解压。 通过obsutil将数据集上传至OBS桶中。 ./obsutil cp ./dog_cat_1w obs://${your_obs_buck}/demo/ -f -r OBS支持多种文件上传方式,当文件少于100个时,可以在OBS Console中上传,当文件大于100个时,推荐使用工具,推荐OBS Browser+(win)、obsutil(linux)。上述例子为obsutil使用方法。
  • Step2 准备训练数据 以Llama-7B为例,全量微调训练使用的是开源的wiki2048数据集。下载数据集之后,需要将开源数据集转化为MindSpore可以读取的数据格式,然后上传到OBS桶中,用于后续的算法训练。 下载WikiText2数据集到用户本地电脑,并转化数据集格式为MindSpore可以读取的数据格式(xxx.mindrecord.db和xxx.mindrecord),具体操作请参见文档。 请参考以下要求创建OBS桶中的文件夹,并上传数据到OBS桶中,用于后续的算法微调训练。 OBS文件夹目录要求如下: {OBS桶} # OBS对象桶,用户可以自定义名称,例如:llm-mindspore-ma -{OBS文件夹} # OBS文件夹,用于存放训练输入数据,用户可以自定义名称,例如:wiki2048 - wiki2048.mindrecord.db # 训练数据集 - wiki2048.mindrecord # 训练数据集 -{OBS文件夹} #训练输出路径,用于存放训练生成的模型文件等,用户可以自定义名称,例如:output -{OBS文件夹} #作业日志路径,用于存放训练日志,用户可以自定义名称,例如:log
  • 同样功能的PyTorch Pipeline,因为指导要求适配onnx pipeline,两个pipeline本身功能就有差别,如何适配? 由于Diffusers社区的“single model file policy”设计原则,不同的pipeline是不同路径在独立演进的。先确保应用输出符合预期后,再进入到MindSpore Lite模型转换的过程,否则迁移昇腾后还是会遇到同样的问题。 父主题: 常见问题
  • 逐个替换模型,检测有问题的模型 该方式主要是通过模型替换,先定位出具体哪个模型引入的误差,进一步诊断具体的模型中哪个算子或者操作导致效果问题,模型替换原理如下图所示。通过设置开关选项(是否使用onnx模型),控制模型推理时,模型使用的是onnx模型或是mindir的模型。 图1 精度诊断流程 一般情况下,onnx模型推理的结果可以认为是标杆数据,单独替换某个onnx模型为MindSpore Lite模型,运行得到的结果再与标杆数据做对比,如果没有差异则说明pipeline的差异不是由当前替换的MindSpore Lite模型引入。 如果有差异,则说明当前模型与原始onnx的结果存在差异。依次单独替换onnx模型为对应的MindSpore Lite模型,从而定位出有差异的模型。在模型初始化的代码块已经添加了use_ascend参数,修改参考如下: 图2 代码修改 以上述现象为例,通过修改use_ascend参数值对模型替换,可以发现:当text_encoder模型为onnx模型,其余模型为mindir模型时,能够得到和标杆数据相同的输出,因此可以判断出转换得到的text_encoder模型是产生pipeline精度误差的根因。通过下一小节可以进一步确认模型精度的差异。
  • 提交训练作业常见问题 报错信息:Exception: You have attempted to create more buckets than allowed 原因分析:由于桶的数量超出限额,无法自动创建。 解决方法:用户可以删除一个桶,或者直接指定一个已存在的桶(修改变量obs_bucket的值)。 报错信息:"errorMessage":"The number of namespaces exceeds the upper limit"或"namespace is invalid" 原因分析:SWR组织数超出限额,SWR组织默认最多只能创建5个组织。 解决方法:用户可以删除一个SWR组织,或者直接指定一个已存在的SWR组织(修改变量image_organization的值)。 报错信息:standard_init_linux.go:224: exec user process caused "exet format error" 原因分析:可能由于训练规格错误导致训练作业卡死。 解决方法:请参考说明查询资源规格。 报错信息:报错镜像失败,报错:401,'Unauthorized',b'{errors":[{"errorCode":"SV CS TG.SWR.4010000",errorMessage":"Authenticate Error",……}] 原因分析:远程连接Notebook时需要输入鉴权信息。 解决方法:传入AK,SK信息。 1 2 3 4 5 6 # 认证用的ak和sk硬编码到代码中或者明文存储都有很大的安全风险,建议在配置文件或者环境变量中密文存放,使用时解密,确保安全; # 本示例以ak和sk保存在环境变量中来实现身份验证为例,运行本示例前请先在本地环境中设置环境变量HUAWEICLOUD_SDK_AK和HUAWEICLOUD_SDK_SK。 __AK = os.environ["HUAWEICLOUD_SDK_AK"] __SK = os.environ["HUAWEICLOUD_SDK_SK"] # 如果进行了加密还需要进行解密操作 session = Session(access_key=__AK,secret_key=__SK, project_id='***', region_name='***')
  • 训练输出保存结构说明 Modelarts训练作业的模型输出和日志信息会定时同步到指定的OBS中,本示例中模型输出路径和日志输出路径分别为f"{default_obs_dir}/mindspore_model/output/"和f"{default_obs_dir}/mindspore_model/logs/",用户可以在OBS中查看训练输出信息。 本示例中训练输出保存在OBS的目录结构如下所示: ${your_bucket} └── intermidiate ├── dataset │ └── flower_photos │ └── flower_photos.zip └── mindspore_model ├── logs │ └── xxx-xxx-xxx--0.log ├── output │ └── 20220627-105226-resnet50-224 └── mindspore-image-models.zip
  • 步骤5:使用SDK提交训练作业 本地调测完成后可以提交训练作业。因为数据在Notebook中,设置InputData中“is_local_source”的参数为“True”,会自动将本地数据同步上传到OBS中。 步骤如下: 在“/home/ma-user/work/models/official/cv/resnet/”下创建train_notebook.py, 复制代码至train_notebook.py, 运行train_notebook.py,进行训练作业提交。 # train_notebook.py # 导入ModelArts SDK的依赖,并初始化Session,此处的ak、sk、project_id、region_name请替换成用户自己的信息 from modelarts.train_params import TrainingFiles from modelarts.train_params import OutputData from modelarts.train_params import InputData from modelarts.estimatorV2 import Estimator from modelarts.session import Session # 认证用的ak和sk硬编码到代码中或者明文存储都有很大的安全风险,建议在配置文件或者环境变量中密文存放,使用时解密,确保安全; # 本示例以ak和sk保存在环境变量中来实现身份验证为例,运行本示例前请先在本地环境中设置环境变量HUAWEICLOUD_SDK_AK和HUAWEICLOUD_SDK_SK。 __AK = os.environ["HUAWEICLOUD_SDK_AK"] __SK = os.environ["HUAWEICLOUD_SDK_SK"] # 如果进行了加密还需要进行解密操作 session = Session(access_key=__AK,secret_key=__SK, project_id='***', region_name='***') # 样例中为了方便默认创建一个OBS桶,推荐将调测所需要传输的文件统一放到`${default_bucket}/intermidiate`目录下,也可以按照注释代码自行指定 obs_bucket = session.obs.get_default_bucket() print("Default bucket name: ", obs_bucket) default_obs_dir = f"{obs_bucket}/intermidiate" #default_obs_dir = "obs://your-bucket-name/folder-name" # 本地的工程代码文件夹路径 code_dir_local = "/home/ma-user/work/models/official/cv/resnet/" #@param {type:"string"} # 代码的启动文件名称 boot_file = "train.py" #@param {type:"string"} train_file = TrainingFiles(code_dir=code_dir_local, boot_file=boot_file) # 本地数据集路径 local_data_path = "/home/ma-user/work/models/dataset/flower_photos" #@param {type:"string"} # 模型输出保存路径 output_local = "/home/ma-user/work/models/official/cv/resnet/output" #@param {type:"string"} # 模拟训练过程中模型输出回传至指定OBS的路径,需要以"/"结尾 obs_output_path = f"{default_obs_dir}/mindspore_model/output/" # 指定一个obs路径用于存储输出结果 output = [OutputData(local_path=output_local, obs_path=obs_output_path, name="output")] # 模拟训练过程中模训练日志回传至指定OBS的路径,需要以"/"结尾 log_obs_path = f"{default_obs_dir}/mindspore_model/logs/" # 训练所需的代码路径,代码会自动从本地上传至OBS code_obs_path = f"{default_obs_dir}/mindspore_model/" data_obs_path = f"{default_obs_dir}/dataset/flower_photos/" # sdk会将代码自动上传至OBS,并同步到训练环境 train_file = TrainingFiles(code_dir=code_dir_local, boot_file=boot_file, obs_path=code_obs_path) # 指定OBS中的数据集路径,会自动将local_path数据上传至obs_path,用户可以在代码中通过 --data_url接收这个数据集路径 input_data = InputData(local_path=local_data_path, obs_path=data_obs_path, is_local_source=True, name="data_url") from modelarts.service import SWRManagement image_organization = SWRManagement(session).get_default_namespace() # image_organization = "your-swr-namespace-name" print("Default image_organization:", image_organization) image_name = "mindspore-image-models-image" #@param {type:"string"} image_tag = "1.0.0" #@param {type:"string"} import os ENV_NAME=os.getenv('ENV_NAME') # 启动训练任务:使用user_command(shell命令)方式启动训练任务 # 注意:训练启动默认的工作路径为"/home/ma-user/modelarts/user-job-dir",而代码上传路径为"./resnet/${code_dir}"下 # --enable_modelarts=True 该代码仓已适配ModelArts estimator = Estimator(session=session, training_files=train_file, outputs=output, user_image_url=f"{image_organization}/{image_name}:{image_tag}", # 自定义镜像 swr地址,由镜像仓库组织/镜像名称:镜像tag组成 user_command=f'cd /home/ma-user/modelarts/user-job-dir/ && /home/ma-user/anaconda3/envs/MindSpore/bin/python ./resnet/train.py --net_name=resnet50 --dataset=imagenet2012 --enable_modelarts=True --class_num=5 --config_path=./resnet/config/resnet50_imagenet2012_config.yaml --epoch_size=10 --device_target="Ascend" --enable_modelarts=True', # 执行训练命令 train_instance_type="modelarts.p3.large.public", # 虚拟资源规格,不同region的资源规格可能不同,请参考“Estimator参数说明”表下的说明查询修改 train_instance_count=1, # 节点数,适用于多机分布式训练,默认是1 #pool_id='若指定专属池,替换为页面上查到的poolId',同时修改资源规格为专属池专用的虚拟子规格 log_url=log_obs_path ) # job_name是可选参数,可不填随机生成工作名 job_instance = estimator.fit(inputs=[input_data], job_name="modelarts_training_job_with_sdk_by_command_v01") 表1 Estimator参数说明 参数名称 参数说明 session modelarts session training_files 训练代码的路径和启动文件 user_image_url 自定义镜像swr地址,由镜像仓库组织/镜像名称:镜像tag组成 user_command 执行训练命令 train_instance_type 本地调测'local'或云端资源规格。每个region的资源规格可能是不同的,可以通过下述说明查询对应的资源规格信息。 train_instance_count 节点数 log_url 日志输出路径 job_name 作业名称,不可以重复 train_instance_type表示训练的资源规格,每个region的资源规格可能是不同的。通过如下方法查询资源规格: 公共资源池执行如下命令查询 from modelarts.session import Session from modelarts.estimatorV2 import Estimator from pprint import pprint # 认证用的ak和sk硬编码到代码中或者明文存储都有很大的安全风险,建议在配置文件或者环境变量中密文存放,使用时解密,确保安全; # 本示例以ak和sk保存在环境变量中来实现身份验证为例,运行本示例前请先在本地环境中设置环境变量HUAWEICLOUD_SDK_AK和HUAWEICLOUD_SDK_SK。 __AK = os.environ["HUAWEICLOUD_SDK_AK"] __SK = os.environ["HUAWEICLOUD_SDK_SK"] # 如果进行了加密还需要进行解密操作 session = Session(access_key=__AK,secret_key=__SK, project_id='***', region_name='***') info = Estimator.get_train_instance_types(session=session) pprint(info) 专属池规格 ModelArts专属资源池统一使用虚拟子规格,不区分GPU和Ascend。资源规格参考表2查询。 表2 专属资源池虚拟规格的说明 train_instance_type 说明 modelarts.pool.visual.xlarge 1卡 modelarts.pool.visual.2xlarge 2卡 modelarts.pool.visual.4xlarge 4卡 modelarts.pool.visual.8xlarge 8卡
  • pipeline应用准备 当前迁移路径是从onnx模型转换到mindir模型,再用mindspore lite做推理, 所以迁移前需要用户先准备好自己的onnx pipeline。下文以官方开源的图生图的Stable Diffusion v1.5的onnx pipeline代码为例进行说明。 进入容器环境,创建自己的工作目录,由于在Snt9B裸金属服务器环境配置指南的配置环境步骤中,在启动容器时将物理机的home目录挂载到容器的“/home_host”目录下,该目录下可以直接使用上传到物理机“home”目录下的文件。本文中,将基于容器的“/home_host”目录创建工作目录: mkdir -p /home_host/work cd /home_host/work 在迁移onnx pipeline前,首先需要确保原始的onnx pipeline能在昇腾机器的ARM CPU上正常执行。进入容器环境后,安装依赖包。 pip install torch==1.11.0 onnx transformers==4.27.4 accelerate onnxruntime diffusers==0.11.1 下载git lfs,用于下载git仓中的大文件。由于欧拉源上没有git-lfs包,所以需要从压缩包中解压使用,在浏览器中输入如下地址下载git-lfs压缩包并上传到服务器的/home目录。 https://github.com/git-lfs/git-lfs/releases/download/v3.2.0/git-lfs-linux-arm64-v3.2.0.tar.gz 安装git lfs: tar -zxvf git-lfs-linux-arm64-v3.2.0.tar.gz cd git-lfs-3.2.0 sh install.sh rm -rf git-lfs-linux-arm64-v3.2.0.tar.gz git-lfs-3.2.0 通过git下载sd pytorch模型。 该模型用于获取模型shape,也可以转换生成onnx模型。后文中的modelarts-ascend仓库已经给出了模型shape,可以直接使用,onnx模型也可以单独下载。 # git clone sd模型 git lfs install mkdir -p /home_host/work/runwayml cd /home_host/work/runwayml git clone https://huggingface.co/runwayml/stable-diffusion-v1-5/ -b main # 将下载的文件夹重命名,以便后续脚本中引用 mv stable-diffusion-v1-5 pytorch_models 这里由于Huggingface网站的限制以及模型文件的大小原因,很可能会下载失败。可以进到Huggingface网站,从浏览器下载模型后,再手工上传到物理机/home/pytorch_models目录下。 通过git下载sd onnx模型。 # git clone sd模型 git lfs install cd /home_host/work/runwayml git clone https://huggingface.co/runwayml/stable-diffusion-v1-5 -b onnx # 将下载的文件夹重命名,以便后续脚本中引用 mv stable-diffusion-v1-5 onnx_models 这里由于Huggingface网站的限制以及模型文件的大小原因,很可能会下载失败。可以进到Huggingface网站,从浏览器下载模型后,再手工上传到物理机/home/onnx_models目录下。 下载好模型后,需要编写推理脚本。为了便于讲解,本指导中所需的代码已发布在ModelArts代码仓,可以使用如下命令下载推理脚本样例代码: cd /home_host/work git clone https://gitee.com/ModelArts/modelarts-ascend.git ll modelarts-ascend/examples/AIGC/stable_diffusion 代码目录如下图所示,onnx_pipeline.py是图生图推理脚本。mslite_pipeline.py、mslite_model_proxy.py、pipeline_onnx_stable_diffusion_img2img_mslite.py是迁移后的文件,其中mslite_model_proxy.py是代理模型类,pipeline_onnx_stable_diffusion_img2img_mslite.py是从Stable Diffusion源码中的pipeline复制并修改的,这些文件在后续的章节中会使用并做进一步讲解。 图1 代码目录 将“modelarts-ascend/examples/AIGC/stable_diffusion/onnx_pipeline.py”文件中的“onnx_model_path”改为步骤6中下载的onnx_models地址“/home_host/work/runwayml/onnx_models”。执行推理脚本进行测试,这里使用的推理硬件是CPU,验证待迁移的代码能在CPU跑通,由于是CPU执行会比较慢,大约需要15分钟左右: cd modelarts-ascend/examples/AIGC/stable_diffusion # 必须执行该命令,否则会报错找不到sketch-mountains-input.jpg python onnx_pipeline.py 生成的图片fantasy_landscape.png会保存在当前路径下,该图片也可以作为后期精度校验的一个对比。 图2 生成图片 父主题: AIGC推理业务迁移指导
  • 加速慢算子的执行速度 首先需要寻找执行速度比较慢的NPU算子列表,Kernel视图包含在NPU上执行的所有算子的信息,主要用于确认高耗时算子。 图7 Kernel视图 推荐基于以下思路尝试优化: 搜索Cast类算子,查看是否Cast类算子最大耗时超过30us或者总耗时占比超过1%,若超过,需尝试启动混合精度训练,详见此处。 图8 Cast类算子 基于Accelerator Core排序,统计AI_CPU算子,如果有AI_CPU类算子执行时长超过1000us或者AI_CPU类算子总执行时长占比超过10%,可尝试修改代码替换API_CPU算子。 需要注意:PyTorch Adaptor针对部分算子,会基于输入类型下发不同运行硬件的算子,所以除了使用同语义算子替换API_CPU算子外,还可以通过修改输入类型使算子下发到API_CORE上(比如torch.topk在参数为一维list使用API_CPU计算,多维参数则基于AI_CORE Vector计算)。 图9 Accelerator Core排序 如果遇到算子运行期间NPU的计算单元和存储单元使用率都未达到80%(查看aiv_*_ratio和aic_*_ratio是否达到0.8),或者算子的“Block Dim”小于AI Core/Vector Core,可尝试使用AOE算子调优,提高NPU硬件资源利用率。 图10 aiv_*_ratio 针对总耗时最长、平均执行耗时最长以及最大耗时的三种排序的TOP算子,可联系华为工程师获得帮助。 图11 耗时排序
  • 模型推理适配 完成模型初始化后,需要将onnx模型推理的代码等价替换为对应的mindir模型推理接口。以vae_encoder模型为例,在pipeline代码中查找vae_encoder推理调用的地方,然后修改为对应的MindSpore Lite版本的推理接口模型。 使用MindSpore Lite Runtime接口替换onnx Runtime接口 # pipeline_onnx_stable_diffusion_img2img_mslite.py … # onnx模型 # init_latents = self.vae_encoder(sample=image)[0] # ----------------修改点----------------- # mslite模型 init_latents = self.vae_encoder_ms(sample=image)[0] ... 替换内嵌模型 # pipeline_onnx_stable_diffusion_img2img_mslite.py … # onnx模型 # image = np.concatenate([self.vae_decoder(latent_sample=latents[i : i + 1])[0] for i in range(latents.shape[0])]) # ----------------修改点----------------- # mslite模型 image = np.concatenate([self.vae_decoder_ms(latent_sample=latents[i : i + 1])[0] for i in range(latents.shape[0])]) ... 修改后的文件参考Gitee代码库中的如下两个文件: pipeline_onnx_stable_diffusion_img2img_mslite.py mslite_model_proxy.py
  • 修改代码依赖 新建并进入/home_host/work/pipeline目录。 mkdir -p /home_host/work/pipeline cd /home_host/work/pipeline 将onnx pipeline依赖的图生图源码“pipeline_onnx_stable_diffusion_img2img.py”复制到该目录下,名称改为“pipeline_onnx_stable_diffusion_img2img_mslite.py”,以便与源文件名称区分。但是这样也会导致无法正确找到源码中相对路径下的依赖,需要将对于diffusers包内的相对路径修改为绝对路径的形式。 图1 代码依赖修改前与修改后 将推理代码“modelarts-ascend/examples/AIGC/stable_diffusion/onnx_pipeline.py”也复制一份到该目录,名称改为“mslite_pipeline.py”,迁移后的推理代码中的pipeline需要修改为从复制的onnx pipeline文件导入: # onnx_pipeline.py from pipeline_onnx_stable_diffusion_img2img_mslite import OnnxStableDiffusionImg2ImgPipeline
  • 运行pipeline代码 pipeline代码如下: # mslite_pipeline.py import os import requests import torch import numpy as np from PIL import Image from io import BytesIO from pipeline_onnx_stable_diffusion_img2img_mslite import OnnxStableDiffusionImg2ImgPipeline def setup_seed(seed): torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) np.random.seed(seed) torch.backends.cudnn.deterministic = True setup_seed(0) # 指定mindir和onnx模型路径 mindir_dir = "/home_host/work/static_shape_convert/mindir_models" onnx_model_path = "/home_host/work/runwayml/onnx_models" os.environ['DEVICE_ID'] = "0" os.environ['TEXT_ENCODER_PATH'] = f"{mindir_dir}/text_encoder.mindir" os.environ['VAE_ENCODER_PATH'] = f"{mindir_dir}/vae_encoder.mindir" os.environ['UNET_PATH'] = f"{mindir_dir}/unet_graph.mindir" os.environ['VAE_DECODER_PATH'] = f"{mindir_dir}/vae_decoder.mindir" os.environ['SAFETY_CHECKER_PATH'] = f"{mindir_dir}/safety_checker.mindir" pipe = OnnxStableDiffusionImg2ImgPipeline.from_pretrained(onnx_model_path, torch_dtype=torch.float32).to("cpu") url = "https://raw.githubusercontent.com/CompVis/stable-diffusion/main/assets/stable-samples/img2img/sketch-mountains-input.jpg" response = requests.get(url, verify=False) init_image = Image.open(BytesIO(response.content)).convert("RGB") init_image = init_image.resize((512, 512)) prompt = "A fantasy landscape, trending on artstation" images = pipe(prompt=prompt, image=init_image, strength=0.75, guidance_scale=7.5).images images[0].save("fantasy_landscape_npu.png") 在运行pipeline时,默认的加速卡为0号卡,当机器有多人使用时,可能存在资源占用而无法正常运行的情况,可以通过环境变量指定加速卡ID,如指定5号卡进行执行。 # mslite_pipeline.py … os.environ['DEVICE_ID'] = "5" … 最后执行python脚本进行推理: #shell python mslite_pipeline.py 图2 执行推理脚本 图3 MindSpore Lite pipeline输出的结果图片
  • 问题定位解决 使用ptdbg_ascend工具dump全网数据,dump接口设置方法具体参考PyTorch精度工具。dump完成后compare GPU和NPU结果进行分析。 dropout算子引入了随机性偏差,如下图: 图2 随机性偏差 根据堆栈信息定位得知dropout是使用的torch.nn.Dropout(),为消除随机性需要将随机因子p改为0或者1,此处是将model_chatglm.py中随机因子改为了0,如下修改: 图3 随机因子改为0 使用ptdbg修改register_hook方式做精度溢出检查。结果显示Tensor___add___233_forward执行时有溢出,这里使用浮点数精度的是 float16,结果显示输入的最大、最小、平均值都为65504(float16的精度范围是-65504 至 65504),如下图所示: 图4 精度溢出检查 因为在NPU下对INF和NAN的支持默认是饱和模式,会将INF置为MAX,NAN置为0,此处Tensor___add___233_forward的输入输出都是fp16的,会将Inf置为65504。 但是在GPU下采用的是INF_NAN模式(保留INF及NAN的结果),所以在做精度对比时先修改 NPU支持模式为INF_NAN模式与GPU保持一致,请参考INF_NAN_MODE_ENABLE。 开启INF_NAN模式方式命令如下: #shell export INF_NAN_MODE_ENABLE=1 修改之后再次做溢出检查显示所有API正常,无溢出情况。 GPU dump数据缺失,从Tensor_transpose_2_forward_output之后没有与NPU对应的bench data数据。 图5 GPU dump数据 在pkl文件中找到对应缺失的位置,发现Tensor_transpose_2_forward_output之后,NPU下一个执行的算子是Tensor_squeeze_0_forward_input,而GPU下一个执行的算子是Tensor___getitem___6_forward_input。 图6 api_stack_dump.pkl 根据stack信息查找到对应源码的代码行,发现对应函数上添加了@torch.jit.script装饰器,经过调试发现,GPU也执行了这个函数,但是没有dump算子执行信息,而且pdb无法在函数中正常中断,删除此装饰器后,GPU能够正常dump数据。 图7 删除@torch.jit.script装饰器 加了@torch.jit.script装饰器,torch_npu能采到数据,而GPU上则不行的原因为:@torch.jit.script装饰器会将装饰函数作为ScriptFunction对象返回,不会产生dump数据。而目前该装饰器在torch_npu下不生效,NPU会按照普通函数执行,因此能够采集到数据。从精度对比角度考虑,先删除@torch.jit.script可以保证这块GPU和NPU dump的数据对齐。 compare表中Cosine列第一个出现偏差的位置,为einsum算子的输入。 图8 Cosine列的偏差 查看堆栈信息发现是self.inv_freq的值存在精度偏差,再追溯到self.inv_freq的定义片段。 图9 inv_freq的定义片段 通过构造该计算公式,发现在x86上:torch+CPU和torch+GPU以及aarch64 torch+NPU场景的结果都是一致的,而aarch64 torch+CPU结果不同,如下: 图10 torch+CPU 图11 torch+GPU 图12 aarch64 torch+NPU 图13 aarch64 torch+CPU 而inv_freq恰好都是在CPU上初始化的。修改NPU版代码,强制使用torch+NPU进行初始化后,可以消除einsum算子输入偏差的问题。修改如下: inv_freq = 1. / (base ** (torch.arange(0, dim, 2).float().npu() / dim)) 另外的一种修改方式是转换到dobule下进行计算。 图14 转换到dobule下进行计算 修复上述问题后,Cosine值第一次出现偏差的位置为permute算子,在backward阶段作为input引入。 图15 permute算子偏差 由于在backward阶段ptdbg-ascend没有输出执行的堆栈信息,先查找了Tensor_permute_0在forward阶段相应的堆栈信息。 图16 Tensor_permute_0在forward阶段相应的堆栈信息 可以得知此处进行了换轴操作,但是在 forward 时输入输出均无精度异常。 因此转换排查思路,全局查找Cosine、MaxAbsErr值和Tensor_permute_0_backward相同的行。发现在Tensor___getitem___490_backward_output.0处MaxAbsErr 的值和Tensor_permute_0_backward一样 图17 Tensor___getitem___490_backward_output.0 并且Bench data列的max、min、mean对应值也一致,但是Tensor___getitem___490_backward_output.0 在NPU下的 max、min、mean值都是0,代表该处是全零的向量。猜想应该是梯度计算错误。使用PyTorch的index_select函数作为getitem函数的替代,对modeling_chatglm.py做如下修改: 图18 modeling_chatglm.py修改 再次dump对比精度,发现该算子精度问题得到解决。 图19 Tensor_permute_0精度对比 图20 算子精度对比 修改上述问题之后,重新对比精度数据后发现,重新进行训练任务,通过对比NPU和GPU的loss曲线,可以发现,两者的下降趋势几乎是一致的。 图21 loss曲线 图中蓝色loss_0是NPU的loss曲线,黄色loss_1是GPU的loss曲线。
  • loss曲线对比 训练结束后,在 output_dir 参数指定目录下会输出 trainer_state.json 文件,该文件保存了训练过程 loss 以及 learning_rate 的 log 信息。 将GPU设备训练输出的 trainer_state.json 文件重命名为trainer_state_gpu.json,并复制到NPU节点的容器内;将NPU设备训练输出的 trainer_state.json 文件重命名为trainer_state_npu.json; 对其进行解析就可以获取 loss 信息,这里可以使用如下脚本进行 loss 曲线的绘制。 # compare_metric.py import json import os from typing import List, Dict import matplotlib.pyplot as plt import numpy as np ## 解析 json 文件 def load_trainer_status(file_path): with open(file_path, "r") as f: trainer_status = json.load(f) return trainer_status.get("log_history") def plot_curve(data_source: List[Dict], tags: List[str]): fig, ax = plt.subplots() for tag in tags: # print(data_source[0], len(data_source[0])) # assert all([tag in status.keys() for status in data_source]), f"Tag {tag} is missing for data source." for index, source in enumerate(data_source): y = [] x = [] for log in source: x.append(log.get("step")) y.append(log.get(tag)) ax.plot(x, y, label=f"{tag}_{index}") ax.legend() plt.savefig("loss.png") if __name__ == "__main__": state_npu_path = os.path.join("trainer_state_npu.json") state_gpu_path = os.path.join("trainer_state_gpu.json") state_npu = load_trainer_status(state_npu_path) state_gpu = load_trainer_status(state_gpu_path) plot_curve([state_npu, state_gpu], ["loss"]) 对比单卡模式下NPU和GPU训练曲线,发现loss曲线下降趋势不一致。这说明迁移的模型存在精度偏差。 图1 loss曲线对比 图中蓝色loss_0是NPU迭代曲线,黄色loss_1是GPU的迭代曲线。
  • 准备数据类似 登录coco数据集下载官网地址:https://cocodataset.org/#download 下载coco2017数据集的Train(18GB)、Val images(1GB)、Train/Val annotations(241MB),分别解压后并放入coco文件夹中。 下载完成后,将数据上传至SFS相应目录中。由于数据集过大,推荐先通过obsutil工具将数据集传到OBS桶后,再将数据集迁移至SFS。 在本机机器上运行,通过obsutil工具将本地数据集传到OBS桶。 # 将本地数据传至OBS中 # ./obsutil cp ${数据集所在的本地文件夹路径} ${存放数据集的obs文件夹路径} -f -r # 例如 ./obsutil cp ./coco obs://your_bucket/ -f -r 登录ECS服务器,通过obsutil工具将数据集迁移至SFS,样例代码如下: # 将OBS数据传至SFS中 # ./obsutil cp ${数据集所在的obs文件夹路径} ${SFS文件夹路径} -f -r # 例如 ./obsutil cp obs://your_bucket/coco/ /mnt/sfs_turbo/ -f -r /mnt/sfs_turbo/coco文件夹内目录结构如下: coco |---annotations |---train2017 |---val2017 更多obsutil的操作,可参考obsutil简介。 将文件设置归属为ma-user chown -R ma-user:ma-group coco
  • 背景信息 Notebook使用涉及到计费,具体收费项如下: 处于“运行中”状态的Notebook,会消耗资源,产生费用。根据您选择的资源不同,收费标准不同,价格详情请参见产品价格详情。当您不需要使用Notebook时,建议停止Notebook,避免产生不必要的费用。 创建Notebook时,如果选择使用云硬盘EVS存储配置,云硬盘EVS会一直收费,建议及时停止并删除Notebook,避免产品不必要的费用。 在创建Notebook时,默认会开启自动停止功能,在指定时间内停止运行Notebook,避免资源浪费。 只有处于“运行中”状态的Notebook,才可以执行打开、停止操作。 一个账户最多创建10个Notebook。
  • run.sh脚本测试ModelArts训练整体流程 自定义容器在ModelArts上训练和本地训练的区别如下图: 图1 本地与ModelArts上训练对比 ModelArts上进行训练比本地训练多了一步OBS和容器环境的数据迁移工作。 增加了和OBS交互工作的整个训练流程如下: 建议使用OBSutil作为和OBS交互的工具,如何在本机安装obsutil可以参考obsutils安装和配置。 训练数据、代码、模型下载。(本地使用硬盘挂载或者docker cp,在ModelArts上使用OBSutil) 启动脚本。(用法无切换,一般就是到达执行目录,然后python xxx.py) 训练结果、日志、checkpoints上传。(本地使用硬盘挂载或者docker cp,在ModelArts上使用OBSutil) 可以用一个run脚本把整个流程包起来。run.sh脚本的内容可以参考如下示例: #!/bin/bash ##认证用的AK和SK硬编码到代码中或者明文存储都有很大的安全风险,建议在配置文件或者环境变量中密文存放,使用时解密,确保安全。 ##本示例以AK和SK保存在环境变量中来实现身份验证为例,运行本示例前请先在本地环境中设置环境变量HUAWEICLOUD_SDK_AK和HUAWEICLOUD_SDK_SK。 ##安装obsutil,完成AKSK配置。建议在基础镜像里做好。 #mkdir -p /opt && cd /opt #wget https://obs-community.obs.cn-north-1.myhuaweicloud.com/obsutil/current/obsutil_linux_amd64.tar.gz #tar -xzvf obsutil_linux_amd64.tar.gz && mv obsutil_linux_amd64_*/ utils #alias obsutil='/opt/utils/obsutil' #obsutil config -i=${HUAWEICLOUD_SDK_AK} -k=${HUAWEICLOUD_SDK_SK} -e=obs.cn-north-4.myhuaweicloud.com ##训练输入复制到容器镜像本地。 #/cache目录的容量较大。 DATA_URL=`echo ${DLS_DATA_URL} | sed /s/s3/obs/` mkdir –p /cache/data /opt/utils/obsutil cp –r –f ${DATA_URL} /cache/data ##执行训练任务。 #涉及conda env切换时。 source /xxxxx/etc/profile.d/conda.sh conda activate xxxenv conda info --envs #启动训练脚本。 cd xxxx python xxx.py ##复制输出结果到OBS目录。 TRAIN_URL=`echo ${DLS_TRAIN_URL} | sed /s/s3/obs/` /opt/utils/obsutil cp –r –f /cache/out ${TRAIN_URL} 把run.sh放到/opt目录,在实际启动任务的时候,使用以下命令启动任务即可: bash –x /opt/run.sh 把run.sh放到/root目录,可以在原镜像里增加一层,这一层就只是COPY这个run脚本。在基础镜像里可以一起把obsutil安装、配置好。参考如下dockerfile: FROM $your_docker_image_tag RUN mkdir -p /opt && cd /opt && \ wget https://obs-community.obs.cn-north-1.myhuaweicloud.com/obsutil/current/obsutil_linux_amd64.tar.gz && \ tar -xzvf obsutil_linux_amd64.tar.gz && mv obsutil_linux_amd64_*/ utils && \ /opt/utils/obsutil config -i=${HUAWEICLOUD_SDK_AK} -k=${HUAWEICLOUD_SDK_SK} -e=obs.cn-north-4.myhuaweicloud.com COPY run.sh /opt/run.sh ModelArts的容器会有一个/cache目录,这个目录挂载的硬盘容量最大。建议下载数据和中间数据都存到这个目录中,防止因硬盘占满导致任务失败。 父主题: FAQ
  • 场景介绍 阅读本文前建议您先了解以下内容: Stable Diffusion的基础知识,可参考Stable Diffusion github、Stable Diffusion wikipedia、diffusers github、Stable Diffusion with diffusers 推理业务迁移到昇腾的通用流程,可参考推理业务迁移通用指导。 由于Huggingface网站的限制,访问Stable Diffusion链接时需使用代理服务器,否则可能无法访问网站。 在Stable Diffusion迁移适配时,更多的时候是在适配Diffusers和Stable Diffusion WebUI,使其能够在昇腾的设备上运行。其中,Diffusers遵循了Huggingface的“single-file policy”的设计原则,它的三个主要模块Pipeline、Schedulers和预训练模型中,Pipeline和Schedulers都完全遵循了“single-file policy”原则。该设计原则更推荐复制粘贴代码而不是做抽象,因此所有和这个模型前向运算相关的源代码都被复制粘贴进同一个文件中,而不是调用某些抽象提取出的模块化的库,Diffusers的这种设计原则的好处是代码简单易用、对代码贡献者友好,但也有明显的缺点,因为这种反软件结构化的设计,导致对于昇腾适配来说比较复杂,因为没有统一的模块化的库可以适配,只能针对每个不同业务的Pipeline进行单独适配。本文以Stable Diffusion v1.5的图生图为例,通过可以直接执行的样例代码介绍Diffusers的昇腾迁移过程,其他的pipeline迁移可以在基于对pipeline的代码充分理解的基础上,参考本文的思路进行举一反三。Stable Diffusion WebUI的迁移不包含在本文中,具体原因详见Stable Diffusion WebUI如何适配?。 AI推理应用运行在昇腾设备上一般有两种方式: 方式1:通过Ascend PyTorch,后端执行推理,又称在线推理。 方式2:通过模型静态转换后,执行推理,又称离线推理。 通常为了获取更好的推理性能,推荐使用方式2的离线推理。下文将以Diffusers img2img onnx pipeline为示例来讲解如何进行离线推理模式下的昇腾迁移。迁移的整体流程如下图所示: 图1 迁移流程图 父主题: AIGC推理业务迁移指导
  • Step2 准备训练数据 ChatGLM-6B-finetune训练使用的是ADGEN数据集,可以从 Tsinghua Cloud下载。下载数据集之后,请参考以下要求创建OBS桶中的文件夹,并上传数据到OBS桶中,用于后续的算法微调训练。 OBS文件夹目录要求如下: {chatglm-mindspore-ma} # OBS对象桶,用户可以自定义名称,例如:chatglm-mindspore-ma -{data} # OBS文件夹,用于存放训练输入数据,用户可以自定义名称,例如:data -{AdvertiseGen} # OBS文件夹,用于存放训练输入数据,用户可以自定义名称,例如:AdvertiseGen - train.json # 训练数据集 - dev.json # 评估数据集 -{OBS文件夹} #训练输出路径,用于存放训练生成的模型文件等,用户可以自定义名称,例如:output -{OBS文件夹} #作业日志路径,用于存放训练日志,用户可以自定义名称,例如:log
  • 昇腾应用样例列表 您可以参考如下Ascend相关案例,使用ModelArts的AI开发能力,支撑您的业务应用。 表1 Ascend样例列表 样例 引擎 对应功能 说明 开源大模型基于ModelArts的一键推理部署 MindSpore Ascend推理 此案例介绍如何从AI Gallery中订阅LLaMA系列和ChatGLM系列开源大模型,并在ModelArts上使用昇腾算力进行推理部署。 LLaMA系列模型基于ModelArts的全参数微调训练 MindSpore 订阅算法(AI Gallery)、Ascend训练、Ascend推理 此案例介绍如何从AI Gallery中订阅LLaMA系列开源大模型算法,并在ModelArts上使用昇腾算力进行微调训练,最终将模型部署为在线服务。 ChatGLM系列模型基于ModelArts的全参微调训练 MindSpore 订阅算法(AI Gallery)、Ascend训练、Ascend推理 此案例介绍如何从AI Gallery中订阅ChatGLM系列开源大模型算法,并在ModelArts上使用昇腾算力进行微调训练,最终将模型部署为在线服务。 推理业务昇腾迁移通用指导 MindSpore Lite 模型迁移、Ascend推理部署 此案例介绍如何将客户已有的推理业务迁移到ModelArts上,使用昇腾算力进行推理部署。 AIGC推理业务迁移指导 MindSpore Lite 模型迁移、Ascend推理部署 此案例介绍如何将客户已有的AIGC推理业务迁移到ModelArts上,使用昇腾算力进行推理部署。 训练业务昇腾迁移通用指导 PyTorch 模型迁移、Ascend训练 此案例介绍如何将客户已有的训练业务迁移到ModelArts上,使用昇腾算力进行训练。 LLM训练业务迁移指导 PyTorch 模型迁移、Ascend训练 此案例介绍如何将客户已有的LLM训练业务迁移到ModelArts上,使用昇腾算力进行训练。 AIGC工具tailor使用指导 MindSpore Lite 模型迁移、Ascend推理部署 此案例介绍如何使用AIGC工具tailor。 示例:从 0 到 1 制作自定义镜像并用于训练(MindSpore+Ascend) MindSpore 制作自定义镜像、Ascend训练 此案例介绍如何从0到1制作Ascend容器镜像,并使用该镜像在ModelArts平台上进行训练。
  • 在本地机器调试 自定义引擎的规范可以在安装有docker的本地机器上通过以下步骤提前验证: 将自定义引擎镜像下载至本地机器,假设镜像名为custom_engine:v1。 将模型包文件夹复制到本地机器,假设模型包文件夹名字为model。 在模型包文件夹的同级目录下验证如下命令拉起服务: docker run --user 1000:100 -p 8080:8080 -v model:/home/mind/model custom_engine:v1 该指令无法完全模拟线上,主要是由于-v挂载进去的目录是root权限。在线上,模型文件从OBS下载到/home/mind/model目录之后,文件owner将统一修改为ma-user。 在本地机器上启动另一个终端,执行以下验证指令,得到符合预期的推理结果。 curl https://127.0.0.1:8080/${推理服务的请求路径}
  • https示例 使用Flask启动https,Webserver代码示例如下: from flask import Flask, request import json app = Flask(__name__) @app.route('/greet', methods=['POST']) def say_hello_func(): print("----------- in hello func ----------") data = json.loads(request.get_data(as_text=True)) print(data) username = data['name'] rsp_msg = 'Hello, {}!'.format(username) return json.dumps({"response":rsp_msg}, indent=4) @app.route('/goodbye', methods=['GET']) def say_goodbye_func(): print("----------- in goodbye func ----------") return '\nGoodbye!\n' @app.route('/', methods=['POST']) def default_func(): print("----------- in default func ----------") data = json.loads(request.get_data(as_text=True)) return '\n called default func !\n {} \n'.format(str(data)) @app.route('/health', methods=['GET']) def healthy(): return "{\"status\": \"OK\"}" # host must be "0.0.0.0", port must be 8080 if __name__ == '__main__': app.run(host="0.0.0.0", port=8080, ssl_context='adhoc')
  • 静态shape模型转换 转换静态shape模型需要在模型转换阶段固定模型的输入shape,也就是说每个输入shape是唯一的。静态shape转换主要包括两种场景: 第一种是待转换onnx模型的输入本身已经是静态shape,此时不需要在转换时指定输入shape也能够正常转换为和onnx模型输入shape一致的mindir模型。 第二种是待转换onnx模型的输入是动态shape(导出onnx模型时指定了dynamic_axes参数),此时需要在转换时明确指定输入的shape。 转换时指定输入shape可以在命令行中指定,也可以通过配置文件的形式进行指定。 在命令行中指定输入shape。 命令行可以直接通过--inputShape参数指定输入的shape,格式为“input_name:input_shape”,如果有多个输入,需要使用“;”隔开,比如“input1_name:input1_shape;input2_name:input2_shape”。 converter_lite --modelFile=./text_encoder/model.onnx --fmk=ONNX --saveType=MINDIR --optimize=ascend_oriented --outputFile=./text_encoder --inputShape="input_ids:1,77" 在配置文件中指定输入shape。 配置文件中通过“[ascend_context]”配置项指定input_shape,格式与命令行一致,多个输入,需要使用“;”隔开;然后在命令行中通过--configFile指定对应的配置文件路径即可。 # text_encoder.ini [ascend_context] input_shape=input_ids:[1,77] 转换命令如下: converter_lite --modelFile=./text_encoder/model.onnx --fmk=ONNX --saveType=MINDIR --optimize=ascend_oriented --outputFile=./text_encoder --configFile=./text_encoder.ini 在使用converter_lite工具转换时,默认是将所有算子转换为fp16,如果想要将固定shape的模型精度修改为fp32进行转换,需要在配置文件中指定算子的精度模式为precision_mode,配置文件的写法如下(更多精度模式请参考precision_mode): # text_encoder.ini [ascend_context] input_shape=input_ids:[1,77] precision_mode=enforce_fp32 对于本次AIGC迁移,为了方便对多个模型进行转换,可以通过批量模型转换脚本自动完成所有模型的转换。 执行以下命令创建并进入static_shape_convert目录。 mkdir -p /home_host/work/static_shape_convert cd /home_host/work/static_shape_convert 在static_shape_convert目录下新建converter_onnx2mindir.sh文件并复制下面内容。其中,onnx_dir表示onnx模型的目录,mindir_dir指定要生成的mindir模型的保存目录。 # converter_onnx2mindir.sh # 设置onnx模型和mindir模型目录 onnx_dir=/home_host/work/runwayml/onnx_models mindir_dir=./mindir_models # 指定配置文件路径 config_dir=/home_host/work/modelarts-ascend/examples/AIGC/stable_diffusion/configs echo "================begin converter_lite=====================" sub_cmd='--fmk=ONNX --optimize=ascend_oriented --saveType=MINDIR' mkdir -p $mindir_dir # rm缓存,慎改 atc_data_dir=/root/atc_data/ # 通用转换方法 common_converter_model() { model_name=$1 echo "start to convert $model_name" rm -rf $atc_data_dir converter_lite --modelFile="$onnx_dir/$model_name/model.onnx" \ --outputFile="$mindir_dir/$model_name" \ --configFile="$config_dir/$model_name.ini" \ $sub_cmd printf "end converter_lite\n" } common_converter_model "text_encoder" common_converter_model "unet" common_converter_model "vae_encoder" common_converter_model "vae_decoder" common_converter_model "safety_checker" echo "================converter_lite over=====================" 转换结果如下,其中safety_checker模型转换成功了,但中间有ERROR日志,这个属于常量折叠失败,不影响结果。 图2 转换结果
  • 获取模型shape 由于在后续模型转换时需要知道待转换模型的shape信息,这里指导如何通过训练好的stable diffusion pytorch模型获取模型shape,主要有如下两种方式获取: 方式1:通过stable diffusion的pytorch模型获取模型shape。 方式2:通过查看代码仓库,根据每个模型的configs文件获取已知的shape大小。 下文主要介绍方式1如何通过stable diffusion的pytorch模型获取模型shape。 在pipeline应用准备章节,已经下载到sd的pytorch模型(/home_host/work/runwayml/pytorch_models)。进入工作目录: cd /home_host/work 新建python脚本文件“parse_models_shape.py”用于获取shape,其中model_path是指上面下载的pytorch_models的路径。 # parse_models_shape.py import torch import numpy as np from diffusers import StableDiffusionPipeline model_path = '/home_host/work/runwayml/pytorch_models' pipeline = StableDiffusionPipeline.from_pretrained(model_path, torch_dtype=torch.float32) # TEXT ENCODER num_tokens = pipeline.text_encoder.config.max_position_embeddings text_hidden_size = pipeline.text_encoder.config.hidden_size text_input = pipeline.tokenizer( "A sample prompt", padding="max_length", max_length=pipeline.tokenizer.model_max_length, truncation=True, return_tensors="pt", ) print("# TEXT ENCODER") print(f"input_ids: {np.array(text_input.input_ids.shape).tolist()}") # UNET unet_in_channels = pipeline.unet.config.in_channels unet_sample_size = pipeline.unet.config.sample_size print("# UNET") print(f"sample: [{2}, {unet_in_channels} {unet_sample_size} {unet_sample_size}]") print(f"timestep: [{1}]") # 此处应该是1,否则和后续的推理脚本不一致 print(f"encoder_hidden_states: [{2}, {num_tokens} {text_hidden_size}]") # VAE ENCODER vae_encoder = pipeline.vae vae_in_channels = vae_encoder.config.in_channels vae_sample_size = vae_encoder.config.sample_size print("# VAE ENCODER") print(f"sample: [{1}, {vae_in_channels}, {vae_sample_size}, {vae_sample_size}]") # VAE DECODER vae_decoder = pipeline.vae vae_latent_channels = vae_decoder.config.latent_channels vae_out_channels = vae_decoder.config.out_channels print("# VAE DECODER") print(f"latent_sample: [{1}, {vae_latent_channels}, {unet_sample_size}, {unet_sample_size}]") # SAFETY CHECKER safety_checker = pipeline.safety_checker clip_num_channels = safety_checker.config.vision_config.num_channels clip_image_size = safety_checker.config.vision_config.image_size print("# SAFETY CHECKER") print(f"clip_input: [{1}, {clip_num_channels}, {clip_image_size}, {clip_image_size}]") print(f"images: [{1}, {vae_sample_size}, {vae_sample_size}, {vae_out_channels}]") 执行以下命令获取shape信息。 python parse_models_shape.py 可以看到获取的shape信息如下图所示。 图1 shape信息
  • 动态分档模型转换(可选) 如果迁移的模型有多个shape档位的需求,可以通过如下方式对模型进行分档转换。 动态分档是指将模型输入的某一维或者某几维设置为“动态”可变,但是需要提前设置可变维度的“档位”范围。即转换得到的模型能够在指定的动态轴上使用预设的几种shape(保证模型支持的shape),相比于静态shape更加灵活,且性能不会有劣化。 动态分档模型转换需要使用配置文件,指定输入格式为“ND”,并在config文件中配置ge.dynamicDims和input_shape使用,在input_shape中将输入shape的动态维度设为-1,并在ge.dynamicDims中指定动态维度的档位,更多配置项可以参考官方文档。 若网络模型只有一个输入:每个档位的dim值与input_shape参数中的-1标识的参数依次对应,input_shape参数中有几个-1,则每档必须设置几个维度。 以text_encoder模型为例,修改配置文件text_encoder.ini如下所示: # text_encoder.ini [acl_build_options] input_format="ND" input_shape="input_ids:1,-1" ge.dynamicDims="77;33" 使用上述配置文件转换得到的模型,支持的输入shape为(1,77)和(1,33)。 然后使用converter lite执行模型转换,转换命令如下: converter_lite --modelFile=./onnx_models/text_encoder/model.onnx --fmk=ONNX --saveType=MINDIR --optimize=ascend_oriented --outputFile=./mindirs --configFile=./configs/text_encoder.ini 若网络模型有多个输入:档位的dim值与网络模型输入参数中的-1标识的参数依次对应,网络模型输入参数中有几个-1,则每档必须设置几个维度。 以unet模型为例,该网络模型有三个输入,分别为“sample(1,4,64,64)”、“timestep(1)”、“encoder_hidden_states(1,77,768)”,修改unet.ini配置文件如下所示: # unet.ini [acl_build_options] input_format="ND" input_shape="sample:-1,4,64,64;timestep:1;encoder_hidden_states:-1,77,768" ge.dynamicDims="1,1;2,2;3,3" 转换得到的模型支持的输入dims组合档数分别为: 图3 组合档数 第0档:sample(1,4,64,64) + timestep(1) + encoder_hidden_states(1,77,768) 第1档:sample(2,4,64,64) + timestep(1) + encoder_hidden_states(2,77,768) 第2档:sample(3,4,64,64) + timestep(1) + encoder_hidden_states(3,77,768) 然后使用converter lite执行模型转换,转换命令如下: converter_lite --modelFile=./onnx_models/unet/model.onnx --fmk=ONNX --saveType=MINDIR --optimize=ascend_oriented --outputFile=./mindirs --configFile=./configs/unet.ini 最多支持100档配置,每一档通过英文逗号分隔。 如果用户设置的dim数值过大或档位过多,可能会导致模型编译失败,此时建议用户减少档位或调低档位数值。 如果用户设置了动态维度,实际推理时,使用的输入数据的shape需要与设置的档位相匹配。
  • Pytorch模型转换为Onnx模型(可选) 获取onnx模型有两种方式,方式一是使用官方提供的模型转换脚本将pytorch模型转换为onnx模型,方式二是对于提供了onnx模型的仓库,可以直接下载onnx模型。下面介绍方式一如何操作,如果采用方式二,可以跳过此步骤。 通过git下载diffusers对应版本的源码。 git clone https://github.com/huggingface/diffusers.git -b v0.11.1 在diffusers的script/convert_stable_diffusion_checkpoint_to_onnx.py脚本中,可以通过执行以下命令生成onnx模型,其中model_path指定pytorch的模型根目录,output_path指定生成的onnx模型目录。 cd /home_host/work python diffusers/scripts/convert_stable_diffusion_checkpoint_to_onnx.py --model_path "./runwayml/pytorch_models" --output_path "./pytorch_to_onnx_models"
  • 性能测试 benchmark工具也可用于性能测试,其主要的测试指标为模型单次前向推理的耗时。在性能测试任务中,与精度测试不同,并不需要用户指定对应的输入(inDataFile)和输出的标杆数据(benchmarkDataFile),benchmark工具会随机生成一个输入进行推理,并统计推理时间。执行的示例命令行如下。 #shell benchmark --modelFile=resnet50.mindir --device=Ascend 在某些推理场景中,模型输入的shape可能是不固定的,因此需要支持用户指定模型的动态shape,并能够在推理中接收多种shape的输入。在CPU上进行模型转换时无需考虑动态shape问题,因为CPU算子支持动态shape;而在昇腾场景上,算子需要指定具体的shape信息,并且在模型转换的编译阶段完成对应shape的编译任务,从而能够在推理时支持多种shape的输入。 绝大多数情况下,昇腾芯片推理性能相比于CPU会好很多,但是也可能会遇到和CPU推理性能并无太大差别甚至出现劣化的情况。造成这种情况的原因可能有如下几种: 模型中存在大量的类似于Pad或者Strided_Slice等算子,其在CPU和Ascend上的实现方法存在差异(硬件结构不同),后者在运算此类算子时涉及到数组的重排,性能较差; 模型的部分算子在昇腾上不支持,或者存在Transpose操作,会导致模型切分为多个子图,整体的推理耗时随着子图数量的增多而增长; 模型没有真正的调用昇腾后端,而是自动切换到了CPU上执行,这种情况可以通过输出日志来进行判断。
  • 自助性能调优三板斧 基于上一步完成的性能测试,为了最大化模型推理性能,首先确保当前使用的CANN版本是最新版本(最新版本请见此处),每个迭代的CANN版本都有一定的性能收益。在此基础上,可以进行三板斧自助工具式性能调优。这些调优过程由大量的项目交付经验总结,帮助您获得模型最佳推理性能,重复性能测试章节可以验证对应的收益情况。 自助性能调优三板斧分别为:通过固定shape获取更好的常量折叠、AOE性能自动调优、自动高性能算子生成工具。 通过固定shape获取更好的常量折叠 在MindIR格式转换时(即执行converter_lite命令时),通过指定具体的静态shape,并且打开--optimize参数指定“ascend_oriented”能够获得更好的常量折叠优化效果。inputShape查看方法请见转换关键参数准备。 Ascend Optimization Engine converter_lite --modelFile=resnet50.onnx --fmk=ONNX --outputFile=resnet50 --saveType=MINDIR --inputShape="input.1:1,3,224,224" --optimize=ascend_oriented 常量折叠是编译器优化中的通用技术之一,在编译节点简化常量表达。通过多数的现代编译器不会真的产生两个乘法的指令再将结果存储下来,取而代之的是会识别出语句的结构,并在编译时期将数值计算出来而不是运行时去计算(在本例子,结果为2,048,000)。 i = 320 * 200 * 32; AI编译器中,常量折叠是将计算图中预先可以确定输出值的节点替换成常量,并对计算图进行一些结构简化的操作,例如ADDN操作,以及在推理过程中的batch normalization操作等。 以BN折叠为例,如下表示折叠后获得的性能收益。 图1 BN折叠下前向运算性能收益 AOE性能自动调优 自动性能调优工具AOE(Ascend Optimization Engine),可以对于模型的图和算子运行通过内置的知识库进行自动优化,以提升模型的运行效率。开启AOE调优后,模型转换时会自动进行性能调优操作,该过程耗时较长,可能需要数小时。 AOE性能自动优化在模型转换阶段进行配置(即执行converter_lite命令时),通过--configFile参数指定配置文件aoe_config.ini,配置文件通过aoe_mode参数指定调优模式。可选值有: "subgraph tuning":子图调优。 "operator tuning":算子调优。 "subgraph tuning, operator tuning":先进行子图调优,再算子调优。 推荐先进行子图调优,再进行算子调优,因为先进行子图调优会生成图的切分方式,子图调优后算子已经被切分成最终的shape了,再进行算子调优时,会基于这个最终shape去做算子调优。如果优先算子调优,这时调优的算子shape不是最终切分后的算子shape,不符合实际使用场景。 本例同时指定了子图调优和算子调优,工具会先进行子图调优,再进行算子调优。 # aoe_config.ini [ascend_context] aoe_mode="subgraph tuning, operator tuning" 指定--configFile=aoe_config.ini即可自动进行性能优化。 #shell converter_lite --modelFile=resnet50.onnx --fmk=ONNX --device=Ascend --outputFile=resnet50_aoe --saveType=MINDIR --configFile=aoe_config.ini 命令执行成功后,性能自动优化前后的性能对比会打印到控制台上,同时会生成更为详细的json格式调优报告。 图2 自动调优输出文件 需要注意的是,并不是所有的模型使用性能自动调优都是有收益的,在本例中,ResNet50模型自动调优收益甚微(模型转换时已经做了部分针对性优化),在有些比较复杂的模型场景下可能会有较好的收益。比如VAE_ENCODER模型使用算子调优收益为11.15%。 图3 VAE_ENCODER模型使用AOE自动调优在屏幕上显示日志 图4 AOE自动调优的输出样例 其中: model_baseline_performance表示调优前模型执行时间,单位为ms。 model_performance_improvement表示调优后模型执行时间减少百分比。 model_result_performance表示调优后模型执行时间。 repo_summary中的信息表示调优过程中使用到的知识库算子个数或者追加到知识库的算子个数。 AOE自动调优更多介绍可参考Ascend转换工具功能说明。 自动高性能算子生成工具 自动高性能算子生成工具AKG(Auto Kernel Generator),可以对深度神经网络模型中的算子进行优化,并提供特定模式下的算子自动融合功能,可提升在昇腾硬件后端上运行模型的的性能。 AKG的配置也是在模型转换阶段进行配置(即执行converter_lite命令时),通过指定对应的配置文件akg.cfg,设置对应的akg优化级别,并且在模型转换时参考样例进行对应的配置。 # akg.cfg [graph_kernel_param] opt_level=2 执行命令: # shell converter_lite --fmk=ONNX --modelFile=model.onnx --outputFile=model --configFile=akg.cfg --optimize=ascend_oriented 自动高性能算子生成工具AKG更多介绍可参考图算融合配置说明和MindSpore AKG。
  • 动态batch 在模型转换阶段通过--configFile参数指定配置文件,并且在配置文件中配置input_shape 及dynamic_dims动态参数。其中input_shape的-1表示动态shape所在的维度,dynamic_dims指定动态维度的取值范围,比如[1~4],[8],[16]表示该动态维度支持1, 2, 3, 4, 8, 16共六种大小。 # config.ini [ascend_context] input_shape=input.1:[-1,3,224,224] dynamic_dims=[1~4],[8],[16] 在执行convert_lite命令时,指定--configFile=config.ini即可自动编译指定的动态shape。 #shell converter_lite --modelFile=resnet50.onnx --fmk=ONNX --device=Ascend --outputFile=resnet50_dynamic --saveType=MINDIR --configFile=config.ini 注意:推理应用开发时,需要使用模型的Resize功能,改变输入的shape。而且Resize操作需要在数据从host端复制到device端之前执行,下面是一个简单的示例,展示如何在推理应用时使用动态Shape。 import mindspore_lite as mslite import numpy as np from PIL import Image # 设置目标设备上下文为Ascend,指定device_id为0 context = mslite.Context() context.target = ["ascend"] context.ascend.device_id = 0 # 构建模型 model = mslite.Model() model.build_from_file("./resnet50_dynamic.mindir", mslite.ModelType.MINDIR, context) data = np.random.rand(8, 3, 224, 224).astype(np.float32) inputs = model.get_inputs() model.resize(inputs, [list(data.shape)]) inputs[0].set_data_from_numpy(data) # 前向推理,并将结果从device侧传到host侧 outputs = model.predict(inputs)[0].get_data_to_numpy() print(outputs.shape) # (8, 1000)
  • 环境准备 开通裸金属服务器资源(请见DevServer资源开通),并在在裸金属服务器上搭建迁移环境请见裸金属服务器环境配置指导。 启动华为云预置镜像环境,本案例使用的贵阳一的镜像环境。 #shell docker run --privileged --name chatglm-test --cap-add=SYS_PTRACE -e ASCEND_VISIBLE_DEVI CES =0-7 -u=0 swr.cn-southwest-2.myhuaweicloud.com/atelier/pytorch_1_11_ascend:pytorch_1.11.0-cann_7.0.1-py_3.9-euler_2.10.7-aarch64-snt9b-20231107190844-50a1a83 bash 此处“-e ASCEND_VISIBLE_DEVICES” 用于指定容器中启动的NPU device,0-7表示从0-7号卡,请按照实际NPU卡情况修改。 安装相关依赖库。 ChatGLM-6B是完全基于Python开发的模型,训练之前需要事先安装与之依赖的Python库。其中部分依赖库可以使用pip工具安装,执行如下脚本: #shell pip install rouge_chinese nltk jieba sentencepiece datasets==2.12.0 fsspec==2022.11.0 transformers==4.29.2 deepspeed==0.9.2 与昇腾NPU适配的依赖库有torch_npu,多卡训练也需要deepspeed_npu,本文适配的版本如下:deepspeed_npu(0.1),torch_npu(1.11)。其中torch_npu 在镜像环境中已经预置安装,deepspeed_npu 安装配置详见 deepspeed_npu。 此外, transformers 执行需要高版本的scikit-learn、acclerate,详见常见问题5、常见问题6。此处执行升级命令: #shell pip install scikit-learn accelerate --upgrade transformers库的training_args.py有部分操作是适配的cuda设备,详见常见问题7,本文使用昇腾ModelZoo的适配版本脚本替换(下载链接)。 下载ChatGLM-6B源代码、模型权重与数据集到容器环境。 源代码:chatglm-6B 模型权重:weights 数据集:Firefly(流萤)、ADGEN (广告生成) 源代码、模型权重使用的清华官方在Github和Hugging Face开源的版本,源代码适配的main分支,权重当前使用 1d240ba 固定分支。其它分支版本理论上也可以进行迁移工作,不过注意可能由于权重不同原因最后训练结果也不太一致,此处建议您使用固定分支进行迁移。 数据集Firefly为本文用于多卡训练使用的数据集,数据集ADGEN为ChatGLM-6B ptuning训练适配的数据集,如果您运行环境为单卡环境下载数据集ADGEN。 父主题: LLM训练业务迁移指导
共100000条