MegEngine 使用小技巧:Profiler使用手册

ByteRiderMaster
• 阅读 440

0.写在前面

“xx,R 那边反应多机训练速度慢,你看一下什么情况”

“xxx,为什么 MGE 更新之后,xxx 网络训练变慢了,你看一下”

这是组内日常对话

然后有人日常背锅

组员的状态是:提性能,提性能,还是 TMD 提性能

据不完全统计,有 80% 的性能问题其实是因为训练代码写的不够好,让 MGE 有力使不出来

包括但不限于以下情况

1)没开 fast_run

2)频繁使用 numpy 进行同步

3)没有用 make_allreduce_cb,让计算通信串行

4)。。。

次数多了,就发现这玩意太花时间了,而且每次的步骤都千篇一律,为啥一定要我来做,所以写这篇文章进行总结,方便大家也方便自己

1.Profiler 介绍

首先我们要认识 Profiler 这个东西

简单来说,Profiler 以时间轴的形式记录了所有算子的运行时间

通过 Profile 结果,我们可以很快的发现这段代码为什么跑的慢

是做了多余的工作?还是计算资源的浪费?或者是算子本身的性能很差,需要替换成别的算子

MegEngine 使用小技巧:Profiler使用手册

这是一个简单的 profile 结果展示

大部分情况下我们只关注 gpu thread,每一个 gpu thread 对应一个 cuda stream,上面都是运行在这个 cuda stream 上的算子

2.使用方式

PS:静态图的统计信息还不够完善(受到图优化影响),profile 结果相对动态图的不够友好

from megengine.utils.profiler import profile, Profiler
 
# 装饰器写法
@profile()
def train_step(data, label, *, optimizer, gm, model)
    with gm:
        logits = model(data)
        loss = F.loss.cross_entropy(logits, label)
        gm.backward(loss)
        optimizer.step().clear_grad()
    return loss
 
# with 写法
# 训练过程中最好只有一个profiler实例,因为profiler会在析构时自动dump出结果,如果有多个实例的话每个iter都会dump,非常慢
profiler = Profiler()
 
def train_step(data, label, *, optimizer, gm, model)
    with profiler:
       with gm:
           logits = model(data)
           loss = F.loss.cross_entropy(logits, label)
           gm.backward(loss)
           optimizer.step().clear_grad()
    return loss

⚠️注意,profiler 默认会在析构的时候导出 profile 结果,也可以手动调用 profiler.dump 方法手动 dump

参数说明:

Profiler 的构造函数支持如下参数:

  • path:  profile 数据的存储路径,默认为当前路径下的 profile 文件夹.
  • format: 输出数据的格式,默认为 chrome_timeline.json ,是 Chrome 支持的一种标准格式,以时间线的形式展现 profiling 结果. 可选项还有 有 memory_flow.svg ,以时间x地址空间的形式展示内存使用情况.
  • formats:  若需要的输出格式不止一种,可以在 formats 参数里列出.
  • sample_rate:  若该项不为零,则每隔 n 个 op 会统计一次显存信息,分析数据时可以绘制显存占用曲线,默认为 0.
  • profile_device:  是否记录 gpu 耗时,默认为 True.
  • with_scopes:  是否额外记录 functional/ tensor method 等 python API 对应的 scope, 默认为 False. 
  • with_backtrace: 是否记录 op/event 对应的 python 调用栈, 默认为 False, 开启会使记录数据文件体积变大.

    scope 使用介绍

我们会自动在 module 的 forward 还有 backward 以及 step 步骤中加入 scope,scope 会在 host thread 上显示,能看出 op 属于哪一个 module 的什么阶段

MegEngine 使用小技巧:Profiler使用手册

也可以自己加上 scope

from megengine.utils.profiler import Profiler, scope
 
def main()
    with Profiler():
        x = Tensor(1)
        with scope("Add"):
            y = x+1
        with scope("Mul"):
            z = x*3

默认情况下 profiler 只会记录 module forward, backward, step 三类 scope, 用户可以在构造 Profiler 对象时传入参数 with_scopes = True 额外记录 functionaltensor methodapi 调用对应的 scope

开启 with_scopes 选项后额外记录了 BatchNorm2d Module 内部调用的 functional / tensor method API 调用 scope

MegEngine 使用小技巧:Profiler使用手册
开启 with_scopes 选项后额外记录了 backward scope, 该 scope 用于记录反向计算序列对应的前向算子, 可用于查找反向计算中有性能问题的算子是由哪种算子前向计算产生。下图 scope 表示 Broadcast, SetSubtensor 等算子是由 Subtensor 反向计算产生的。

MegEngine 使用小技巧:Profiler使用手册

3.可视化显示

推荐使用 perfetto 查看 profile 结果,也可以用 Chrome 开发者模式(F12)的 Performance 模块查看 timeline 格式文件,也可以用 [chrome://tracing/]() 进行查看

以下介绍的都是基于 perfetto 的操作方式

1)统计

可以选中一段连续的时间段,查看这一个时间段的统计结果

下方会显示事件统计结果,可以看到事件实际占用时间(Wall duration)(可以结合总时间算出空闲时间),可以按照总占用时间排序,也可以按照平均时间排序

MegEngine 使用小技巧:Profiler使用手册

2)依赖关系

在 host thread上,op 会记录对应的 input和output 以及相应的依赖关系,可以依据箭头找到 input 依赖的上一个 op,也可以通过下方 flow event 点击移动到上一个或者下一个

MegEngine 使用小技巧:Profiler使用手册

MegEngine 使用小技巧:Profiler使用手册

我们还能找到 op 对应的 host 时间和 gpu 时间,点击 op 可以看到在不同 thread(cpu,gpu)占用的时间

MegEngine 使用小技巧:Profiler使用手册

3)查看显存使用, gpu 利用率等指标

profiler 除了记录时间算子执行时间外,还会记录一些与显存和性能相关的指标。gpu_usage_ratio 记录程序执行平均的 gpu 利用率(gpu 执行 kernel 时间占总时间的比例),gpu_usage_ratio 低说明程序 host 侧是瓶颈 。gpux:x alloc_mem_value 记录了gpux 显存使用量随时间的变化的曲线, 需要把 sample_rate 设置为大于 0 的整数(sampe rate 代表每隔 n 个 op 记录一次显存使用量)

MegEngine 使用小技巧:Profiler使用手册

4)放大缩小

可以拖动上方时间轴的起始和结束点来修改起始点和结束点,也可以通过放大缩小手势进行放大缩小

中间竖线上面的灰色小方块就是可以拖动的点

MegEngine 使用小技巧:Profiler使用手册
MegEngine 使用小技巧:Profiler使用手册

4.常见调试技巧(附使用例子

1)多余计算

MegEngine 使用小技巧:Profiler使用手册
yolox 例子,forward,backward,step 运行完成了,但是后面多出了很多的 reshape 操作(一般认为 reshape 无实际计算,所以基本看作是浪费

找到原因后结果如下(5s->1.3s)

MegEngine 使用小技巧:Profiler使用手册

2)计算通信串行(请认准 make_allreduce_cb)

allreduce 通信在 gpu0:1,如果发现通信在 gpu0:0 那就是用错了

MegEngine 使用小技巧:Profiler使用手册

3)host 性能慢,gpu 利用率不高

MegEngine 使用小技巧:Profiler使用手册

cpu 时间和 gpu 时间基本上一致,很可疑

放大仔细看,gpu 运行时间中有很多空隙,而且点击对应 op 查看依赖关系,可以看出中间的空隙时间是在等待 host 进行 launch cuda kernel

MegEngine 使用小技巧:Profiler使用手册

4)使用  backtrace 记录功能查找性能瓶颈部分对应源码

上述示例介绍了如何从 profile 结果中发现性能异常的部分, profiler 提供了 backtrace 调用栈记录功能, 方便用户找到异常部分对应的训练代码源码。backtrace 记录会记录算子的 dispatch/kernel 执行,TensorWaitProperty 等事件对应的 python 调用栈。 

可以在构造 Profiler 对象时通过传入 with_backtrace = True 开启调用栈记录功能。 开启该选项后 profiler 保存数据文件体积会增大。

用户可以在 perfetto UI 界面上点击 op 查看其对应的源码。

下图 profiler 结果中 CompiledOp[IOU] 算子执行时间较长, 通过记录的 backtrace 可以发现该算子是检测模型计算 loss 部分调用的。

MegEngine 使用小技巧:Profiler使用手册

下图中 interpreter 线程中某个 TensorWaitProp 占用时间很长,可能会拖慢 host 执行速度,导致 gpu 空闲。

(TensorWaitProp 可能是由 tensor.shape, tensor.numpy() 等方法调用产生的, 会让 host 侧等待 device 执行,以获取 Tensor 的 value 或 shape 属性)

通过调用栈可以发现该事件是由 basedet 检测模型 get_ground_truth 方法中的某个 getitem 产生的 ( __getitem__中使用了 tensor shape 属性触发了 host 侧的 sync)。
MegEngine 使用小技巧:Profiler使用手册

更多 MegEngine 信息获取,您可以:查看文档和 GitHub 项目,或加入 MegEngine 用户交流 QQ 群:1029741705。欢迎参与 MegEngine 社区贡献,成为 Awesome MegEngineer,荣誉证书、定制礼品享不停。

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Wesley13 Wesley13
4年前
Java日期时间API系列31
  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前
Stella981 Stella981
4年前
Apache Synapse 远程代码执行漏洞(CVE
!(https://oscimg.oschina.net/oscnet/435fc4cde65d4aee9a2efca3080cb89e.png)0x00事件背景ApacheSynapse是一个简单、轻量级的高性能企业服务总线(ESB),它是在ApacheSoftwareFoun
Stella981 Stella981
4年前
Python Challenge Level 18
初学Python,挑战一下流行的PythonChallenge,很不幸,卡在了18关~~被字符字节码之间的转换搞得焦头烂额,不过终于搞定了还是很happy的~~~主要的问题就是16进制形式的字符如何转成字节码(注意:不是encoding)如:\'89','50','4e','47','0d','0a','1a','0a','00
Wesley13 Wesley13
4年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
4年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
4年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
4年前
Uber准备放弃自动驾驶,转手卖给前谷歌无人车CTO,估值曾被孙正义炒到72.5亿美元
!(https://oscimg.oschina.net/oscnet/0fe7cb00a0cf4872b022342d1e21d47e.png)杨净发自凹非寺量子位报道|公众号QbitAI最新消息,Uber要出售无人驾驶部门(ATG)了。据TechCrunch报道,Uber有意向出售,而也有人愿意买。
Python进阶者 Python进阶者
2年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这