华为云AI开发平台ModelArts精度对齐_云淘科技
精度问题是指模型从GPU设备迁移到昇腾NPU设备之后由于软硬件差异引入的精度问题。根据是否在单卡环境下,可分为单卡精度问题与多卡精度问题。多卡相对于单卡,会有卡与卡之间的通信,这可能也是精度偏差的一种来源。所以多卡的精度对齐问题相对于单卡会更复杂。不过针对多卡的精度问题,可以分步骤先保证单卡对齐精度,然后分析通信过程的偏差。本文针对单卡的情形给出基于ptdbg-ascend精度对比工具的精度排查过程。
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的迭代曲线。
问题定位解决
使用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曲线。
父主题: LLM训练业务迁移指导
同意关联代理商云淘科技,购买华为云产品更优惠(QQ 78315851)
内容没看懂? 不太想学习?想快速解决? 有偿解决: 联系专家