[DevLog]24Q3P1 - Optimize PTA With Thread Affinity
240923
上午
- T1: 硬装回来,编译与环境复原,代码同步。
- T2: 问题:cpprinter编译错误 - 有额外的构造函数未定义。
- 使用
nm
查看了声明和调用文件的.o
文件的函数,发现不匹配,推测是使用时的__FUNCTION__
参数类型不匹配。
- 使用
- T3: 修复:重新编译。
- T4: 发现参数虽然都是std::string,但是由于库不匹配,所以还是构造函数还是不match,尝试将参数改成更简单的char *,重新编译。
下午
- T1&2: 问题:Bus error (core dumped)
- gdb发现是
tIC
的多线程内存管理的问题,注释掉相关代码,重新编译。
- gdb发现是
- T3: 问题:SegFault
- gdb发现是类CallTrace(内部有static变量)指针的delete报错,虽然不是特别理解,但是改用unique智能指针。
- T4: unique还是segfault,改成shared_ptr
- T5: shared_ptr还是segfault,CallTrack类只是为了包装一下,其实都可以static,所以把指针改成static
- T6: 能运行小例子了,但是functionName是乱码, 并且start时间是1970-01-04。看上去是析构的时候,这两个变量的值丢失了导致的,但是奇怪的是cmake的例子没有这个问题?
- static变量解决这个问题
晚上
- T1&T2: 精简了stack,
- T3:分析线程,发现现象有待研究
- T4:[x] OpenSora无法运行,torch的
run_backward
报错result.use_count() <= 1
。回到硬装前留下的问题了。稍微看了对应的代码,是codegen的部分的。- 是DEBUG选项的问题。
240924
上午
- T1/2/3/4: 分析OpenSora失效点,二分法测试多种情况
- T5: 分析清楚当前的数据,测试下一步:在op_api add插入cpprinter, 并跑完一轮,来确定使用了哪些线程
- 如猜想一样,就是主线程和反向线程。
- T6: 后续修改策略:其实都不用看代码,在设置好数据和模型后,执行模型,会利用torch里注册的PTA的算子来执行。至于训练或者推理的入口,肯定是在pytorch端,PTA只是被动被调用下发算子,
- 显示打印,setAffi的结果。
- 策略,一开始赋值对应NPU的全部(除开前向和反向的两个),后面单独设置前向和反向
| time | Thread 1 | Thread 2 | Thread 3 | Thread 4 | Thread 5 | Thread 6 |
| ----- | --------------------------- | ------------ | ----------- | ------------- | --------------- | -------------------------------- |
| 51:58 | NSysCtrl::Init setName&Affi | | | | | |
| 52:49 | | Cons setName | | hcclC setName | | |
| 52:50 | | | Rel setName | | | |
| 53:03 | | Cons setAffi | Rel setAffi | | | |
| 54:15 | | | | | | NSysCtrl::ExchangeDevice setAffi |
| 54:31 | **op_api::add** | | | | | |
| 56:50 | | | | | NGuardI setAffi | |
| 56:53 | | | | | **op_api::add** | |
| 56:59 | | | | hcclC setAffi | | |
* 现在看上去 Thread 1 也就是主线程就是前向,Thread 5就是反向线程。还有待全流程测试验证猜想。
* 如果上面正确,那么反向线程很好设置,`NGuardI setAffi`的位置基本就是反向开始的位置。
下午
- T1/2/3/4: 思考&编码
- T5: 重编, 任务排队
- T6: 客户需求,编码添加自定义的绑定设置。
晚上
- T1: 测试获得3%的收益,OpenSora 1p测试 前向 0.385s -> 0.375s
- 发现至少还有两个线程在工作,
- 测试llama情况
- 编码
- 由于其余的线程占用还比较高,将已知的
NSysCtrl::ExchangeDevice
绑核测试 - 编码8p和非
device 0
的情况,
- 由于其余的线程占用还比较高,将已知的
240925
上午
- T1:8:50 机器8p都被占用了; 重新编译昨天修改好的
- T2: 学习bash的函数传参
- T3&4: 编写bash脚本,调试8p例子
- T5: 问题:8p情况下htop感觉绑定到同一个核上了,需要仔细看一看
下午:
- T1, env=1时程序有bug,会中断;同时需要答应8p的绑核信息。
- T2, 修复环境变量为空时的bug
- T3&T4: 修复逻辑,重新编译,中途别人任务占用,宕机,卡死频繁。
- T5: 重新编译和修复bug
240926
上午
- T1: 问题:多NPU时无法实现分区,
- 首先测试了怡文原本的配置,发现能正常分区,说明之前的
SetAffi
能有效利用device_id
来分区。 - 是static修饰变量初始化的原因。
- 首先测试了怡文原本的配置,发现能正常分区,说明之前的
- T2: 问题:删除了怡文
static const
竟然导致了seg fault - T3&4: OpenSora 8p * 2 测试
- T5&6: 看如何测试llama,和脚本?
下午
- T1: llama脚本通过修改conda的transform包来计时
- T2:测试发现demo_ll.py在千次循环时,会劣化 48ms->58ms, 不清楚是我的问题(git 8f3714),还是v2.1.0本身的问题。
- T3: 代码回退到修改前,测试初始基准。是v2.1.0本身的问题,确实会劣化;
- T4&T5: 测试了每日包,也存在劣化的问题。
- T6: 版本转测让路
当前的结论:
1. 暂时无法确定是哪里的问题,pytorch,PTA,还是机器硬件、OS调度的问题。
怡文:
title 1
model_inputs = self.prepare_inputs_for_generation(input_ids, **model_kwargs)
需要前提移动到循环外,不然每次迭代input_ids会变大。
晚上
- T1&2 实验 llama多step劣化的原因
- T3&T4 程序会在432步时,突然变慢 47->57ms
240927
上午:
- T1&2&3&4&5 继续测试和可视化数据,观察劣化原因
- 发现TASK_QUEUE_ENABLE=2/3 会延迟劣化到1900步左右
- TODO:400步的时候save,然后重新跑,来区分是运行环境还是数据的原因
- T6 OpenSora数据可视化,数据十分稳定,不会像llama那样波动
- pgo二次编译就是先编译执行一次,获取指令执行信息(分支路径),来指导第二次编译
下午
- T1-5:Ascend质量誓师大会:硬件、HDK、CANN、MindIE(新推理)、文档维护都问题多多,尤其是硬件质量导致很多集群问题,软件问题注意集中在:
- 没有考虑到用户的可能情况:墨菲定律,又译为摩菲定律,原句是:如果有多种方式去做某事,而其中一种方式将导致灾难,则必定有人会这样选择。在科学和算法方面与英文所谓的“worst-case scenario”
- 错误码缺失,定位不明。
- 软件问题难以定位
- T6:llama的
demo_ll.py
在修改成npu:1
时,性能劣化严重, 开启TQ2,代码是默认v2.1.0- npu0
34ms
, + taskset 0-2331ms
- npu1
45ms
, + env 1 严重劣化到140ms
;观察htop发现,主线程在numa0,但是其余PTA辅助线程在numa1;猜测主线程numa不对是主要原因;潜在危险:这个特性应该被我继承了。 - 需要测试我的修改
- npu0
- 好多人用,今天又是严重排队的一天。
- 能不能写成内部文档,外部的能写的太少了。
- 原本以为可以用draft来实现公开和私有的隔离,但是mkdocs不支持,
- docuwiki的方法也不行,服务器的网络限制上传
- 首先需要测试华为的服务器能不能正常搭建内部网页, 我原本以为想2012一样,能轻易实现。绿区服务器是不行的,搭建了本地电脑也访问不了。
240929
上午
- T1&T2: 排队,我真是急死了,测试不了,怎么改啊。
- 服务器上搭建网站也不行。
- T3&4&5: 总结思路,统计数据,重新补测。
下午
- T1-5: 编译安装torch,来支持PTA的DEBUG模式。来熟悉简单add算子的dispatch流程。
- T6-8:
undefined symbol
也是之前cpprinter
遇到的问题,编译环境竟然不同。导致多了c++11的选项。
240930
上午
- T1-3: 确定是ABI不匹配的问题,尝试出编译的顺序逻辑:两个都先编译好才能安装
- T4: 排队, 顺利GDB,但是超级缓慢,半小时才启动的那种。
241008
- 导师说,环境变量设置参考之前的。
- 并且绑核要
Ascend_logd
记录,通过export ASCEND_GLOBAL_LOG_LEVEL=0
开启, 同时设置export ASCEND_PROCESS_LOG_PATH=$HOME/log/
- 安全设置,先是粗粒度NUMA绑
241012
上午
- T1-3: 后续要求开发。编写了策略
下午
- 代码去除cout cerror,换成官方的LOG
- 根据新的环境变量格式来修改代码。
- 和导师汇报了劣化的可能,并重新测量了OpenSora 的时间,
- 整体env2: 258, env1:233, env0: 253
- 35steps: env2: 258, env1:233, env0: 253
- 问题:
- env0 也使用了env2
- 为什么训练step加速了,但是总时间劣化了。可以考虑训练前后使用api启动
- 程序的函数栈相当的厚重:aclLOG:debug模式下,OpenSora总时间819,中间python从执行第一条指令到结束726s,py后还需要32s,py前需要31s
- 能不能加速编译,只变异修改的.o和最后的.so。 是不是只要make就行。
241013
下午
- 打印输出,分析env0错误原因:简化了脚步,重新编译了
1. 新问题:OpenSora找不到log,需要从简单例子开始验证。
2. 原因:是LOG,默认有覆盖功能,最多生成10个20MB的文件,老的会被删除。可以通过变量DeviceMaxFileNum控制,但是还是建议从简单的开始。
2. 还有个warning写成debug了
3. 打印函数有错误, 原因是string.c_str()
4. 快速编译 make torch_npu/fast
241014
上午
- T1-3:阅读3ms的团队文档
- T4-5: 分析昨晚数据
- env2 平均 253,env2 隔离核 平均256,env1 劣化严重 493,env0 稳定 238
- 问题一:env1 劣化严重。原因是之前留下的MAP接口有其他的作用,导致限制了
- 分析step前后时间,前后确实有些许劣化
- env2 253 = 31 + 180 + 43 (不可信)
- env1 493 = 33 + 418 + 42 (不可信)
- env0 233 = 19 + 191 + 23
- 问题二:env2的区间不是[],需要重测。
下午
- T1: 重新测试数据,可信
1 | 1. env2 258 = 30 + 181 + 46 (default 主线程绑1,其余0-23) |
241015
- 上午T1-4: 编写自动化时间线脚本,等机器
- 下午转测完,环境/驱动变了,hccl初始化报错
- 晚上跑了
- PPT:PyTorch主线程(PTA主线程)、二级流水线程、数据加载进程。
- 晶博的问题:数据加载不是线程是进程,然后变慢的到底是什么操作启动了线程,不要从实验结果解释。
241016
- 上午:开了一上午的24Q4展望
- 下午:支持华为云项目对齐,编写文档,编写定时脚本。
241017
- 上午:计划实现怡文说的暴露接口还原,和环境变量统一格式,
- 没机器待测试
- 下午:测试了,错误还是有但是不是我的问题:env0也有
- 完成了子线程、进程、multiprocessing的测试
- 晚上:时间过长:
- py前应该是import内容的构造/init, 确实在torchrun 8p时,会受到线程conf影响env1或者2的影响,
- 具体来说是
from colossalai import booster
, 最终调用了torch_npu._C._npu_init
, 在8p时,粗或者细粒度绑核,会导致初始化边长7.8s
->19.8s
- 具体来说是
- py后是对象的析构
- py前应该是import内容的构造/init, 确实在torchrun 8p时,会受到线程conf影响env1或者2的影响,
- TODO:
- 稍微看下init代码(C++ Profile)
- 切分训练前时间,特别是dataloader和torch_npu的初始化(viztracer in detail,env选项对dataloader影响甚微)
- 将setAffinity从setDevice里移出来,绑在setName的PTA线程相关的地方。以最小对py前后的影响。
241018
- crontab没触发?等机器,分析尾部的时间, 猜测是atexit挂载的钩子 的
torch_npu._C._npu_shutdown
,说明不是的- 实验 env2
torch_npu._C._npu_shutdown
7.6s ,尾部共 42s (没开启了reset_threads_affinity) - 实验 env2
torch_npu._C._npu_shutdown
12.5s ,尾部共 48s (没开启了reset_threads_affinity) - 实验 env2
torch_npu._C._npu_shutdown
12.9s ,尾部共 29s (开启了reset_threads_affinity) - 实验 env0
torch_npu._C._npu_shutdown
7.9s ,尾部共 26s - 说明不是的, 但是说明
reset_threads_affinity
还是有效的。
- 实验 env2
- 尾部通过monkey patching拦截
- env2 结尾48s = 20s + atexit(12s + 6s同步波动) + 10s (没开启了reset_threads_affinity)
- env2 结尾26s = 5s + atexit(8s + 1s同步波动) + 11s (开启了reset_threads_affinity)
- env0 结尾31s = 5s + atexit(12s + 4s同步波动) + 10s
- env0 结尾22s = 4s + atexit(7s + 4s同步波动) + 7s
- 说明
reset_threads_affinity
还是有效的, 并且生效的区域是atexit
前的地方。
- TODO:
- C++ Profile
torch_npu._C._npu_init
- 将setAffinity从setDevice里移出来,绑在setName的PTA线程相关的地方。以最小对py前后的影响。
- C++ Profile
241021
- 之前代码思路:torch控制的线程,利用了setDevice的接口,在HOST进程与NPU设备绑定的时候设置亲和性。
- 值得注意的是,这只会触发一次,虽然触发位置多(e.g.,
RegisterNPU::wrapper_npu_empty_strided()
),但是会static thread_local int local_device==device
,然后跳过。 - 思路是很巧妙的,因为这将torch上层的线程创建时设置亲和性(PTA感知不到),变成了每个线程(利用
thread_local
)初次设置/变设备(静态变量来保证一次)时设置亲和性
- 值得注意的是,这只会触发一次,虽然触发位置多(e.g.,
- 反向算子新线程的逻辑是:torch为反向算子创建了新线程(暂时代码不明)到新设备上,这个线程要处理很多算子,第一个算子触发通过了setDevice, 并且是新线程,所有设置新的亲和性。
- 主线程一开始init会SetDevice,后面算子也会调用SetDevice,只不过被跳过了。但是之前的SetName生效了,所以导致所有线程被设置为NPUGaurd的名字。
- 重构动机:绑核导致,init变慢了,具体原因???
- 重构思路:将setAffinity从setDevice里移出来,来跳过init时触发的SetDevice。但是你就找不到算子下发的时机了。
- 另一种思路,一开始设置全部就行,虽然不影响dataloader,但是运行时怎么隔离?
- 需要研究的,
c10_npu
与PyTorch的c10
的DeviceGuard实现强相关,需要看torch代码
- 要做PPT了!!!(做了一晚上)
241022
- epoch间的dataloader时间没有统计,分析dataloader的调用栈
- 写PPT
- 实验:
- epoch里的dataloader从第二次开始需要并行,1p会从2s劣化到8s
- 8p总时间变长了,原因是第一步的backward极具劣化,15s->35s。这是新修改引入的。
- 难道是这时候亲和性设置,导致数据移动。
- 需要看vizTracer堆栈,dataloader,backward
- 严格监控侵入式修改处的执行情况(调用次数,时机,时间 - cpprinter), 不打印栈,太耗时了.
- 晚上:研究bash python cmake profile策略
241023
- ninja用不了,ccache用上,cPython 显示98时间还是submodule调用(其中有个generate_code占用了大部分时间68%)
- 测量yiwen的编译选项
-fstack-protector-strong
和-fstack-protector-all
的区别 - 当前担心点:
- 初始化变慢,新写法解决了初始化,但是第一次step fallback慢了10s
- 需要分析堆栈
- 本身env0就慢,先不管
- step里的dataloader时间没有统计进去,感觉时间出问题了
- 重点,python侧profile:epoch0 初始化,epoch1 就开始fork和join了,重点在于进程阻塞在一个核
- C++侧,dataloader调用了PTA的什么呢?
- 预估方案,在dataloader和save前,reset_threads
- 重点,python侧profile:epoch0 初始化,epoch1 就开始fork和join了,重点在于进程阻塞在一个核
- 训练后线程恢复实现了接口
torch_npu._C._reset_threads_affinity()
,但是不是好方法. - 提升没有那么大,波动却十分大,加上没机器,和别人干扰。难以判断各种代码和配置的优劣。
- 初始化变慢,新写法解决了初始化,但是第一次step fallback慢了10s
241027
- 之前几天中期答辩PPT重做,详设文档,民主生活会
- 周日加班:cpprinter完成预期的功能,和PTA结合良好。
- 问题:dataloader迭代结束后,为什么重新init了。
- 批量实验探究一个问题: 虽然按照时间隔离里,那之前的空间隔离还有用吗,应该是有用的 unknown 5-24。隔离了其余的操作。
- 实验结果: 在重构的代码下,下发阶段主线程绑单核是肯定的正收益; 值得探究的是下发阶段unknown隔离的效果
- 隔离了,下发计时是变快了一点,但是 step 0 的backward貌似一定会慢10s(稳定性有待测量),导致总时间会略有劣化,
- 有待大量实验确认
- 难以解释。
241028
- 整理了实验数据:unknown隔离在下发阶段平均提升 0.16%, 属实有点小,端到端还下降 1%。就不修改了。
- 先统一环境变量解析,验证正确性。
- 再分支出
v2.1.0_synrecoder
合并yiwen的commit, - 再测试mf的环境。
- 再分支出
- 流水线测试:申请权限,加__init__.py里修改环境变量打包。
- TMG的PPT,先给晶博看,再参会。
241029
- 上午
- T1-T3: 测试了自定义选项的异常值,通过。
- todo
- 再分支出
v2.1.0_synrecoder
合并yiwen的commit, - 再测试mf的环境。
- cman L2 缓存编译
- 自验实验报告(四个模型) + 完善设计文档,归档设计文档。
- 流水线测试:申请权限,加__init__.py里修改环境变量打包, 冒烟自测。通过
- BCG协议签署 + 还机器
- 出差房租等申请
- 再分支出
241030
- 上午: L2数据预取cman,读代码编译,发现系统内核不支持。
- 下午:
- mf环境熟悉,
- kQueueCapacity变大,同步时间变长,下发变快了。
- 测试两个小模型
- 安排四个模型的运行
- 预取寻优
- mf环境熟悉,
241031
- 紧急:
- 实验结果:OpenSora1.1 不达预期, llama没实现细粒度绑核。
- 改进: llama需要重新profile寻找,
- acl_thread启动的时候,已经到新设备了,这里setMainThread
- OpenSora1.1 不达预期:
- jinbo说,dataloader一直会运行。 unknown设置5-23,先尝试隔离:
- 发现小问题:一开始主线程都是在NUMA1 上,后来都切换了。
- 但是acl_thread启动的时候,已经到新设备了,这里setMainThread。
- 怡文分支领先许多,要么同步,要么重新和自己对比
- 把py里的环境变量控制去掉。
- 下午:
- 根因是mf的脚本一层套一层,在最里面固定是粗粒度绑核了。
- 实现了上面的修改,提升在7%左右。
- TO DO
- 出差房租等申请
- 自验实验报告(四个模型)+ 完善设计文档,归档设计文档。
- 同步主仓,并且cherry-pick到v2.1.0, pull 主仓, reset到主仓,然后push,
- 最后PR
- L2 分区和预取寻优。
- 910C的效果。晚上测试
241101
- OpenSora 1.2 不及预期,check过了,算子下发时各个核心正常绑定了,
- 测了两对数据,时间产生了env1 < env2 < env1 < env2 的顺序。
- 变成前19step 测试
- 安装和测试49 的910C的环境,并定时。
241102
- ip 49 的910C在与mf的基准对比的情况下,效果确实不错
- 910C的env1 的llama基准测试,自测env1是24左右。
- 184的OS2还是不达预期
- env2 和 1 step19 129 要批量测试一次。
- step129 确信是没提升,甚至劣化1%。
- step19 env1 更快了。先确定step
- 晶博的意见:测试第二层循环间的数据处理时间?
- 阅读MM代码
- 复制一份,然后viztracer内容-> 判断mf划的范围是不是大了。
- yiwen同步的原理:
- 好奇怪,通信时间为什么这么上下变化?
- 预期是E2E有收益,sync时间是大致不变的(但是绑核会不会变近,然后变小)。
- env2 和 1 step19 129 要批量测试一次。
- TO DO
- 出差房租等申请
- 自验实验报告(四个模型)+ 完善设计文档,归档设计文档。
- 还有llama 3完全没测试。mf说很快。
- 同步主仓,并且cherry-pick到v2.1.0, pull 主仓, reset到主仓,然后push,
- 最后PR
- L2 分区和预取寻优。
- 出支持包3.10
241104
- 上午拍昇腾宣传短片,当背景板
- L2 Partition
- 下午:L2 Partition,尝试了多种策略,最好的策略提升6%。
- todo
- llama 3测试
- 184劣化测试
241105
- llama 3 确实有劣化,理解了同步的时间计算
- 由于难以解释劣化,并且llama3 跑的不慢,启用原本的MAP变量来调试DEBUG
- 发现unknown绑全核就没问题了,
- 难以解释
- 920C的绑核虽然没有劣化,但是有隐藏问题:
- 开了多线程,NUMA的划分变了。
- 920C的卡是8卡16chip,torch_run 看npu-smi会使用前7个chip
241106
- 等PR的CI,合入代码
- UT 2.5 失败4次, 代码监视打回三次。
- 184的劣化问题,貌似不严重了。发现OS的优化没有还原。
241007
- 合入5分支,发转测邮件。
- 重新测量184的llama3基准
- 下午拍戏,科目一准备。
环境变量文档
CPU_AFFINITY_CONF
使用说明
功能描述:
在 PyTorch 训练或在线推理场景下,可以通过此环境变量控制 CPU 端算子线程的处理器亲和性(即线程绑核)。该配置能够优化线程的执行效率,避免跨 NUMA(非统一内存访问架构)节点的内存访问,减少线程调度开销。
可选参数:
mode:<value>
绑核模式,取值如下:- 1:将进程下的所有线程绑定在 NPU 对应的 NUMA 节点的 CPU 核组上,避免跨 NUMA 节点的内存访问。
- 2:在
mode:1
的基础上进一步优化, 将进程中的主要线程锚定在 NUMA 节点的某颗固定核心上,减少线程在同一NUMA节点内不同CPU。 - 其他值表示不启用绑核功能(默认不开启)。
npu<value>:<value>-<value>
自定义 NPU 的绑核范围:- 例如,
npu0:0-2
表示运行在编号为0
的 NPU 上的进程会绑定到编号为0
、1
、2
的 CPU 核心。 - 默认情况下,
mode
参数为1
时此设置生效,并可用于覆写mode:1
时的绑核策略。比如有两张卡npu0
和npu1
,对于设置export CPU_AFFINITY_CONF=mode:1,npu0:0-1
,npu0
的绑核策略会被覆写,而npu1
则保持mode:1
的默认绑核策略。
- 例如,
参数配置格式:
CPU_AFFINITY_CONF=<option1>:<value1>,<option2>:<value2>
说明:
- NUMA 节点对应的 CPU 核组可以通过命令
lscpu
查看。 - 默认情况下,
npu0
或Device 0
对应的核组是NUMA0
。
配置示例:
示例一: 将所有线程绑定到 NPU 对应的 NUMA 节点:
1
export CPU_AFFINITY_CONF=mode:1
示例二: 将主要线程固定在指定的 CPU 核心上:
1
export CPU_AFFINITY_CONF=mode:2
示例三: 自定义多张 NPU 卡的绑核范围:
1
export CPU_AFFINITY_CONF=mode:1,npu0:0-1,npu1:2-5,npu3:6-6
DEBUG
- 感兴趣的地方打断点,观察触发时机,频率,函数栈,
- 通过
info threads
命令查看所有线程的状态,并使用thread <thread_id>
切换到特定线程进行调试。
1 | // 程序的执行逻辑: 初始化,算子下发,复原 |
数据
- llama 十组数据平均
49.7816
ms ->49.3209
ms, 提升0.9%
- OpenSora 1p 三组平均 前向部分算子下发提升
1.84%
, E2E提升1.44%
- OpenSora 8p 三组平均 前向部分算子下发提升
1.53%
, E2E提升3.16%
总结
- 目标与动机:通过细粒度绑核,使得算子下发线程能在最亲和的核上独占调度,一方面避免竞争,一方面避免OS线程切换导致的开销,使得算子下发的速度加快。
- 难点与发现:
- 理清楚pytorch和PTA的线程的工作情况,并找到并对目标线程进行设置。
- 经过实验发现,线程的亲和性设置是会被创建的子线程继承的:
- 如果在不合适的地方(过早)对线程进行细粒度的亲和性设置,会对子线程也同样设置,产生非预期的影响。比如,导致父子线程竞争同一个核,导致性能大幅劣化。
- PTA下发前向算子的线程就是pytoch的主线程,如何在不修改torch代码的情况下设置pytoch的主线程的亲和性。
- 同时需要对线程实现及时复原,
- 理解与对策:
- 纳入考量的全部线程,除了PTA的线程,还包括pytorch的灵活调度的线程池。
- 目标线程确定为, 主线程为前向算子下发线程,NPUGaurd线程为反向算子下发线程。
- 两阶段的线程设置:
- 初始阶段为其余线程分配核:虽然不能修改torch代码,但是可以利用在初始化阶段torch对PTA的调用,将非目标线程绑定在其余核上。
- 算子下发阶段再分配:观察到算子下发阶段会初始化NPUGuard,在此时刻重新分配目标线程的独占核。
- 负面:细粒度绑核需要额外计算和syscall,单次
0.6s
, 调用大约6~7次,增加耗时5s左右 - 潜在危险 - 难以复原环境:
- 由于结束训练,不再使用算子,在被调用的PTA侧是难以感知的,故难以找到时机复原线程的独占设置。
- 这导致在算子执行完之后,如果又进行数据读取之类调用torch线程池的操作,由于PTA在算子开始执行时绑定到一个核,如果主线程新创建子线程会导致无法并行读取数据。
- 潜在生成情况:
device 0
不默认插在numa 0的情况。
算子下发优化
OS优化
- 机器在空载的时候,还有260GB的内存占用,是分配了大页内存
free -h
和cat /proc/meminfo |grep huge
可以看见Hugetlb: 262144000 kB = 250GB
参考文献
- 怡文W3的博客
- w3 & wiki
[DevLog]24Q3P1 - Optimize PTA With Thread Affinity