更新时间:2024-09-14 GMT+08:00
分享

PyTorch迁移精度调优

完成代码迁移适配后,用户需要进一步验证训练精度是否达标。在保证迁移正确的前提下,迁移后精度偏差的来源,一方面是昇腾设备部分算子的实现和CUDA算子有差异,另外一方面则是硬件方面的差异,如Ascend Snt9芯片上的Matmul和Conv等cube算子只支持FP16,可能会导致数值溢出,从而引起精度误差。此外,网络随机参数初始化差异以及典型场景(比如dropout和数据集shuffle等操作)都可能引入误差,所以迁移模型精度校验以及精度调优的工作至关重要。

精度校验

迁移之后的精度校验工作是以CPU/GPU环境训练过程作为标杆的,这里的前提是在迁移前,模型已经在CPU/GPU环境达到预期训练结果。在此基础上,迁移过程的精度问题一般包括:

  • loss曲线与CPU/GPU差异不符合预期。
  • 验证准确度与CPU/GPU差异不符合预期。

在迁移到NPU环境下训练发现以上问题时,说明精度可能存在偏差,需要进一步做精度调优。下文将分别阐述精度诊断的整体思路和借助工具如何进行精度问题的定位。

精度调优总体思路

精度问题定位首先要能在昇腾环境上稳定地复现问题,大模型训练通常使用多机训练,而多机训练复现问题的成本通常较高,且直接使用工具可能会产生TB级的大量dump数据,存储和复制都比较困难,所以建议用户在复现前先进行模型裁剪,例如减小模型层数(通过num_layers参数控制)、将模型转为单机训练等,这样会大大降低后续定位的难度。在问题复现后,可以进一步根据问题现象选择对应的工具辅助定位,包括溢出检测工具、API预检工具、整网dump比对工具等,通过多组试验比较标杆(GPU/CPU)环境和昇腾环境上运行训练时的差异点来判断问题所在、整体流程如下图所示,更多介绍可参考昇腾精度调试指南

图1 精度调优流程

溢出检测和dump比对是通过在PyTorch模型中注入hook从而dump模型训练过程的输入输出数据,比对NPU环境和标杆环境的所有输入输出的差异来发现异常信息。更多介绍可参考精度比对工具ptdbg-ascend

API精度预检是通过提取模型中所有的API前反向信息,通过工具构造相应的API单元测试,将NPU输出与标杆比对,从而检测出精度有差异的API。更多介绍可参考精度预检工具api_accuracy_checker

准备工作

在定位精度问题之前,首先要排除其他因素的干扰。目前大部分精度无法对齐的问题都是由于模型超参数、Python三方库版本、模型源码等与标杆环境(GPU/CPU)设置的不一致导致的,为了在定位过程中少走弯路,需要在定位前先对训练环境及代码做有效排查。此外,问题定位主要基于GPU环境和NPU环境上运行的过程数据做对比,所以需要分别准备GPU和NPU训练环境,大部分场景需要规模相同的训练环境,如果已经将模型缩减到单机可运行,则只是单台GPU设备即可。

定位前的排查当前主要包含如下几个方面:

  1. 训练超参数。常见的超参如下图所示:
    图2 训练超参数

    模型的超参通常可能调整的主要有学习率,batch size,并行切分策略,学习率warm-up,模型参数,FA配置等,用户在进行NPU精度和GPU精度比对前,需要保证两边的配置一致。

    a. 学习率:lr

    b. batch size, micro batch size

    batch size会影响训练速度,有时候也会影响模型精度。micro batch size会影响流水线并行中设备的计算效率。

    c. 切分策略:DP、TP、PP

    DP:data parallel

    数据并行(data parallelism)是大规模深度学习训练中常用的并行模式,它会在每个进程(设备)或模型并行组中维护完整的模型和参数,但在每个进程上或模型并行组中处理不同的数据。因此,数据并行非常适合大数据量的训练任务。

    TP:tensor parallel

    张量并行也叫层内并行,通过将网络中的权重切分到不同的设备,从而降低单个设备的显存消耗,使得超大规模模型训练成为可能。张量并行不会增加设备等待时间,除了通信代价外,没有额外代价。

    PP:pipeline parallel

    流水线并行将模型的不同层放置到不同的计算设备,降低单个计算设备的显存消耗,从而实现超大规模模型训练。流水线并行也叫层间并行,层输入输出的依赖性使得设备需要等待前一步的输出,通过batch进一步切分成微batch, 网络层在多个设备上的特殊安排和巧妙的前向后向计算调度,可以最大程度减小设备等待(计算空泡),从而提高训练效率。

    d. 学习率预热

    不同的学习率调度器(决定什么阶段用多大的学习率)有不同的学习率调度相关超参,例如线性调度可以选择从一个初始学习率lr-warmup-init开始预热。可以选择多少比例的训练迭代步使用预热阶段的学习率。不同的训练框架有不同的参数命名,需要结合代码实现设置对应的参数。

    e. 模型结构

    配置模型结构的超参主要有num-layer、hidden-size;、seq-length等。

    f. FA配置:use-flash-attn。

  2. 训练脚本

    由算法迁移人员排查迁移后的NPU脚本是否存在问题,可以通过beyond compare工具比对GPU训练脚本和NPU训练脚本之间是否存在差异。例如是否GPU环境下开启了FA但是NPU上未开启FA。

  3. 三方库版本比对

    大模型训练通常会使用deepspeed、megatron等三方库,需要确保这些三方库的版本一致。

  4. 环境版本更新

    这一项仅在条件允许的情况下进行,根据精度问题定位经验,部分问题是由于使用了较早版本的昇腾软件版本或者非商用发布的昇腾软件版本,所以推荐在条件允许的前提下配套安装最新商发版本的昇腾开发套件CANN Toolkit、昇腾驱动以及torch_npu包。参考昇腾商用版资源下载指导

  5. 数据集。

    需要排查是否使用的训练数据集存在差异。

  6. 初始权重。

    需要排查是否加载的初始权重有差异,建议加载相同的初始权重。

问题复现

一般场景的训练模型都是包括随机种子、数据集shuffle、网络结构dropout等操作的,目的是在网络阶段引入一定的随机性使得训练结果更加具有鲁棒性。然而在精度诊断或者对齐阶段,这些随机性会导致训练运行结果每次表现不一致,无法进行和标杆的比对。因此在训练模型复现问题时,需要固定存在随机性的步骤,保证实验可重复性。存在随机性的步骤包括模型参数初始化,数据batch加载顺序,dropout层等。部分算子的计算结果也存在不确定性,需要固定。

当前固定随机性操作可分为工具固定人工固定两种。

  1. 工具固定(seed_all)

    对于网络中随机性的固定,ptdbg工具提供了seed_all接口用于固定网络中的随机数。如果客户使用了工具但取用了其他随机种子,则必须使用客户的随机种子固定随机性。

    函数原型

    seed_all(seed=1234, mode=False)

    表1 参数说明

    参数名

    说明

    是否必选

    l seed

    l 随机数种子。参数示例:seed=1000。默认值为:1234。

    l 否

    l mode

    l 确定性计算模式。可配置True或False。参数示例:mode=True。默认为False。

    l 即使在相同的硬件和输入下,API多次执行的结果也可能不同,开启确定性计算是为了保证在相同的硬件和输入下,API多次执行的结果相同。

    l 确定性计算会导致API执行性能降低,通常不需要在精度问题刚开始定位时就开启,而是建议在发现模型多次执行结果不同的情况下时再开启。

    l rnn类算子、ReduceSum、ReduceMean等算子可能与确定性计算存在冲突,若开启确定性计算后多次执行的结果不相同,则考虑存在这些算子。

    l 否

    函数示例

    seed_all函数的随机数种子,取默认值即可,无须配置;第二个参数默认关闭,不开启确定性计算时也无须配置。

    确定性计算是NPU的一套机制,用于保证算子的计算确定性。之所以要有这个机制,是为了在debug过程中,让所有的算子计算结果前后完全一致可复现,这是大多数精度问题分析的重要前提。因此,在精度问题定位过程中,确定性计算不是目的,而是手段,很多场景下要在确定性计算使能的情况下,进行下一步的精度问题分析定位。cuda对部分算子实现了确定性计算,但仍有部分算子无法固定。通常需要依赖确定性计算的场景是长稳问题,因为长稳问题需要通过多次长跑来分析Loss情况,这时候如果NPU本身计算结果不确定,就难以支撑和GPU结果的多次对比。

    l 示例1:仅固定随机数,不开启确定性计算

    seed_all()

    l 示例2:固定随机数,开启确定性计算

    seed_all(mode=True)

    除此以外,还需要添加以下环境变量,固定通信算子计算的确定性:

    export HCCL_DETERMINISTIC=TRUE

    固定随机数范围

    seed_all函数可固定随机数的范围如下表。

    API

    固定随机数

    l os.environ['PYTHONHASHSEED'] = str(seed)

    l 禁止Python中的hash随机化

    l random.seed(seed)

    l 设置random随机生成器的种子

    l np.random.seed(seed)

    l 设置numpy中随机生成器的种子

    l torch.manual_seed(seed)

    l 设置当前CPU的随机种子

    l torch.cuda.manual_seed(seed)

    l 设置当前GPU的随机种子

    l torch.cuda.manual_seed_all(seed)

    l 设置所有GPU的随机种子

    l torch_npu.npu.manual_seed(seed)

    l 设置当前NPU的随机种子

    l torch_npu.npu.manual_seed_all(seed)

    l 设置所有NPU的随机种子

    l torch.backends.cudnn.enable=False

    l 关闭cuDNN

    l torch.backends.cudnn.benchmark=False

    l cuDNN确定性地选择算法

    l torch.backends.cudnn.deterministic=True

    l cuDNN仅使用确定性的卷积算法

  2. 工具固定(dropout)

    dropout的实质是以一定概率使得输入网络的数据某些维度上变为0,这样可以使得模型训练更加有效。但在精度问题的定位过程之中,需要避免产生这种问题,因此需要关闭drop_out。

    在使用from ptdbg_ascend import *后,工具会自动将如下接口参数p(丢弃概率)置为0。

    torch.nn.functional.dropout

    torch.nn.functional.dropout2d

    torch.nn.functional.dropout3d

    torch.nn.Dropout

    torch.nn.Dropout2d

    torch.nn.Dropout3d

  3. 人工固定(硬件随机差异)

工具内部对于随机的控制,是通过设定统一的随机种子进行随机性固定的。

但是由于硬件的差异,会导致同样的随机种子在不同硬件上生成的随机数不同。具体可以看下面示例:

图中可见,torch.randn在GPU和NPU上固定随机种子后,仍然生成不同的随机张量。

对于上述场景,用户需要将网络中的randn在cpu上完成后再转到对应device。比如StableDiffusion中需要在forward过程中逐步生成随机噪声。

这样在host侧生成的随机张量能够保证一样,搬移到npu或者gpu设备上仍然一样。

固定随机性完成后,可以使用缩小的模型在单机环境进行问题复现。复现后使用下一章节介绍的工具进行问题定位。这里需要注意的是,部分模型算法本身存在固有的随机性,在使用上述方法固定随机性后,如果使用工具也未能找到出问题的API,需要分析是否由算法本身的随机性导致。

API预检工具使用说明

对于任何问题场景都推荐先使用预检工具,检查第1个step或loss明显出现问题的step。它可以抓取模型中API输入的数值范围,根据范围随机生成输入,用相同的输入分别在npu(gpu)和cpu上执行算子,比较输出差异。预检最大的好处是,它能根据算子(API)的精度标准来比较输出结果并判定其是否有精度问题,所以不需要使用者做任何额外分析,而且基本不会出现误检的情况,使用门槛较低。预检工具使用包含以下三步:dump、run_ut以及api_precision_compare。

1)dump这一步主要是为了获取整网中每个pytorch 计算API的输入真实张量数值、shape、 dtype以及数值分布。

2)run_ut这一步可以根据dump输出数据完成NPU vs CPU高精度(标杆)或者GPU vs CPU高精度(标杆)的单API测试,并输出预检结果。

3)api_precision_compare是预检结果的比对,需要同时获取NPU和GPU环境下run_ut的结果文件进行比对,输出最终的比对结果。

该工具的使用指导请参考api_accuracy_checker

精度比对工具使用说明

ptdbg_ascend是昇腾开源的用于PyTorch框架迁移训练的精度对比工具。使用时需要两组模型运行环境,一组是基于昇腾AI芯片的NPU环境,另一组是CPU/GPU环境(标杆环境)。ptdbg_ascend通过在PyTorch训练脚本中插入dump接口,跟踪计算图中算子的前向传播与反向传播时的输入与输出,然后再compare将对比结果写出到.csv表格中。

当前支持计算Cosine(余弦相似度)、MaxAbsErr(最大绝对误差)和MaxRelativeErr(最大相对误差)这三种评价指标,通过设定相似度阈值和最大绝对偏差限来判断API运行时是否存在精度问题。

  1. 安装ptdbg_ascend工具。

    下载最新的whl包至服务器并复制至运行的容器环境中(下载链接),通过pip安装ptdbg_ascend工具。

    #shell
    pip install ./ptdbg_ascend-{version}-py3-none-any.whl

  2. 获取NPU和GPU的dump数据。

    在单卡场景下,PyTorch训练脚本插入dump接口方式如下:

    # 导入ptdbg_ascend依赖包
    from ptdbg_ascend import register_hook, overflow_check, seed_all, set_dump_path, set_dump_switch, acc_cmp_dump
    # 在 main 函数中固定随机数
    seed_all(seed=1234, mode=False)
    # 设置 dump 文件保存路径
    set_dump_path("./npu_dump", dump_tag='all')
    # 添加hook函数和数据比对dump开关
    register_hook(model, acc_cmp_dump)
    # dump 开启和关闭。在一个 iter 的开始和结束位置设置
    set_dump_switch("ON", mode="api_stack", filter_switch="OFF")
    # ---------
    # iterartion
    # ---------
    set_dump_switch("OFF", mode="api_stack", filter_switch="OFF")

    以上给出的是dump整网精度数据的方式,在迁移过程中也会遇到数值溢出问题。在ptdbg中设置检查精度溢出方式如下:

    # dump 开启和关闭。在一个 iter 的开始和结束位置设置
    register_hook(model, overflow_check, overflow_nums=3)
    set_overflow_check_switch("ON")
    # ---------
    # iterartion
    # ---------
    set_overflow_check_switch("OFF")

    这里的overflow_sum用来控制溢出次数,表示多少次溢出时停止训练。

  3. 生成精度对比表。

    dump得到NPU和GPU数据的之后,会得到保存api完整输入输出Tensor的“.npy”文件以及保存API简单统计信息的“.pkl”文件。在compare对比时,需要分别指定NPU和GPU的pkl文件路径和dump数据路径(具体参数请按实际路径填写),compare.py实现如下:

    # compare.py
    from ptdbg_ascend import compare
    dump_result_param= {
    "npu_pkl_path": "${dump_data_npu}/api_stack_dump.pkl",
    "bench_pkl_path":"${dump_data_gpu}/api_stack_dump.pkl",
    "npu_dump_data_dir": "${dump_data_npu}/api_stack_dump/",
    "bench_dump_data_dir": "${dump_data_npu}/api_stack_dump/",
    "is_print_compare_log": True
    }
    compare(dump_result_param, "./output", stack_mode=True)

    执行comapre.py对比之后会在output目录下输出compare_result_{timestamp}.csv文件。根据此文件,可以查看网络训练过程中各个API执行的输入输出以及评价指标的信息。

  4. 根据堆栈信息定位代码差异点。

    在compare对比表文件NPU_Stack_Info列,有各个算子在执行时的堆栈信息。如下图所示,列出的是NPU下Torch_embedding_0_forward(代表torch的embedding算子在第 0 次执行forward阶段)的堆栈信息。

    图3 堆栈信息

    部分情况下也需要查看GPU训练时的堆栈信息来排除GPU和NPU是否执行的不同逻辑代码。这种情况可以根据NPU Name在dump阶段生成的.pkl文件中查找。在ptdbg-ascend中,有支持使用API接口parse堆栈信息的方式,代码如下:

    # compare.py
    from ptdbg_ascend import parse
    parse(". /dump_data /npu/rank0/api_stack_dump.pkl", "Torch_embedding_0_forward")

  5. 精度对齐。

    ptdbg-ascend当前支持的评判指标有Cosine、MaxAbsError(可参见接口函数说明):

    精度存在异常的情况包含以下三种情况:

    • Cosine < 0.99且MaxAbsError > 0.001
    • Cosine < 0.9
    • MaxAbsError > 1

    其余情况都视为达标。精度对齐时,需要根据compare表格查找精度不达标的算子进行调整优化。由于算子间可能存在前后数据传输的相关性,一般先定位第一个不达标的算子,然后结合堆栈信息进行分析和调整,调整之后重新训练dump数据再做对比,直至模型训练的loss曲线和在验证集上做测试的结果和GPU标杆结果一致为止。

相关文档