PyTorch fastrnns 基准测试深入指南:TorchScript RNN 的正确性校验、性能对比与 nvprof 剖析
2026/9/7 19:04:03 网站建设 项目流程

PyTorch fastrnns 基准测试深入指南:TorchScript RNN 的正确性校验、性能对比与 nvprof 剖析

【免费下载链接】pytorchTensors and Dynamic neural networks in Python with strong GPU acceleration项目地址: https://gitcode.com/GitHub_Trending/py/pytorch

benchmarks/fastrnns是 PyTorch 仓库中专门面向 RNN 与 TorchScript 的基准测试与剖析工具集。它以「同一套问题规模、同一套参数语义」为出发点,对原生 cuDNN LSTM、纯 ATen 实现、多种 TorchScript 变体以及纯 Python 手写 RNN 进行前向/反向的计时对比,并验证各实现之间的数值一致性。读完本文,你将掌握这套工具的运行方法(正确性测试、性能基准、nvprof 剖析)、每个命令行参数的取值与含义,以及藏在源码背后的 ModelDef 抽象与 runner 注册机制,从而能够在自己的机器上复现结果并正确解读它们。

本文以 benchmarks/fastrnns/README.md 为主线,并结合benchmarks/fastrnns/下各源码文件展开讲解。目录下的包名fastrnns意味着相关命令需要在benchmarks目录下执行(通过python -m fastrnns.xxx以包方式加载)。

工具定位:测什么,为什么用这一套模型矩阵

README 开篇点明这套基准面向的是TorchScript 模型("Benchmarks for TorchScript models")。其核心价值在于:同一份 LSTM 语义可以被写成多种形态,而它们在实际运行时的性能差异巨大。通过统一脚本,可以横向对比:

  • cudnn:基于torch.nn.LSTM模块、由 cuDNN 加速的参考实现;
  • aten:同一模块但在运行期间关闭 cuDNN(依赖 ATen 算子);
  • jit系列:把手写 LSTM 展开逻辑用torch.jit.script编译后的变体,包括jit_premul(预先乘输入权重)、jit_premul_bias(预乘并一次性加 bias)、jit_simple(扁平输入、兼容旧版 JIT 的写法)、jit_multilayer(多层);
  • py:完全不走 JIT、纯 Python 控制流实现的手写 LSTM。

除 RNN 外,runner 还注册了resnet18/resnet18_jit/resnet50/resnet50_jit等 CNN 模型用于对比(见 runner.py 中nn_runners字典,resnet 的创建函数也复用同一套 ModelDef 接口)。

运行前的环境准备:追求"最稳定结果"的三个动作

README 对希望获得可复现计时结果的用户给出三条建议(主要针对 Linux x86 环境):

  1. 将 CPU Governor 设为性能模式(performance),避免省电模式(energy save)下调频带来的抖动;
  2. 关闭所有 CPU 的 Turbo Boost(假定是 Intel CPU),防止瞬时睿频抬高单次计时;
  3. cset shield屏蔽(隔离)CPU,把负载钉在指定核心上,减少被调度器迁移与其他进程干扰的可能性。

README 同时给出 Caveats:使用 Linux 才能得到最精确的计时;而且这批测试中有相当一部分只在 CUDA 上运行。这与源码相互印证——test.py 在devicecuda时如果 CUDA 不可用会直接抛出 AssertionError;bench.py 默认device="cuda"并在 CUDA 上使用torch.cuda.Event计时、每轮torch.cuda.synchronize();而 profile.py 甚至硬性要求device == "cuda"。因此没有 NVIDIA GPU + CUDA 环境时,许多条目无法运行。

另外 README 坦承:部分脚本虽然接收命令行参数,但"大多数不接受,因为作者偷懒了",并说明默认尺寸已经比较合理。实际上通过后续迭代,bench.py 与 profile.py 都已经支持了完整的参数控制(下文详述),真正未开放参数的其实是底层factory/cells等封装,README 这句话更接近一段历史注脚,读者不必担心参数不可配。

包内模块速览:读懂每个文件的分工

在动手运行前,先弄清benchmarks/fastrnns/下各文件职责,有助于理解后面每条命令内部发生了什么:

文件职责
runner.py维护nn_runners注册表:模型名 →(name, creator, context)context提供运行期上下文切换能力
factory.py各种模型creator工厂,统一产出ModelDef(含输入、参数、forward/backward 入口)
cells.py纯张量运算实现的手写 LSTM/GRU/RNN cell(LSTM 门控、premul 变体、MILSTM 等)
custom_lstms.py用 TorchScript 编写自定义 LSTM 的辅助类与script_lstm/script_lnlstm工厂
bench.py主基准入口,fwd/bwd 计时并输出表格或 JSON
test.py以原生pytorch_lstm_creator为对照,校验各实现 fwd+bwd 的一致性
profile.py包裹nvprof生成.nvvp剖析文件
fuser.py集中配置 JIT fuser(te/old/none/default)与 executor
test_bench.py + conftest.pypytest-benchmark 集成(可选运行方式)

注意目录下的init.py 导出了 cells 与 factory 的符号,并把默认问题尺寸seqLength=100 / numLayers=2 / inputSize=512 / hiddenSize=512 / miniBatch=64作为模块级常量暴露。

第一步:验证 fastrnns 正确性(fwd + bwd)

命令与默认行为

README 给出的正确性测试命令:

python -m fastrnns.test

也可以指定只测某类实现:

python -m fastrnns.test --rnns jit

阅读 test.py 的入口(L155起)可知:若不显式传--rnns,默认值是["jit_premul", "jit"]。也就是说,默认只校验两种 JIT 变体与原生 LSTM 的一致性。

测试方法论:一切以原生 LSTM 为"对照组"

test.py 的test_rnns用一条清晰的三段式流程校验被测实现与pytorch_lstm_creator(基于torch.nn.LSTM)产出的对照模型:

  1. Precondition(前件一致性):先断言被测模型与对照模型的输入inputs、参数params逐张量相等;
  2. Check outputs(前向):同一输入分别前向,断言输出的最大绝对偏差不超过阈值0.001assertEqual(tensor - expected).abs().max() > threshold即失败,失败会进入barf()进入 pdb 调试);
  3. Check grads(反向):通过backward_setup构造梯度(默认以随机grad_output对 hidden 求导),各自backward后比较所有requires_grad参数(p.grad)的梯度一致性。

测试对控制流、数据形状有严格约定:所有实现共享同一seqLength=100 / numLayers=1 / inputSize=512 / hiddenSize=512 / miniBatch=64 / device=cuda / seed=17的默认参数(test_rnns默认值以及命令行--seed默认17)。

对于变长序列 LSTM,test_vl_py走的是另一条路径:由于vl_py(纯 Python 变长实现)与vl_cudnnpack_padded_sequence路径)输出格式不同,无法直接逐元素对齐,因此先用nn.utils.rnn.pad_sequence将变长输出补齐成 padded 形式再与 cuDNN 输出比较,同时用torch.cat拼接逐样本的隐状态后比较。这也印证 README 提到的——各实现"可比较性"本身就是一个需要专门处理的问题

测试入口还校验:若devicecudatorch.cuda.is_available()为 False,直接报错退出。通过--rnns可组合传入多个名字(如cudnn aten jit),对每个 runner 都会循环执行一遍test_rnns

第二步:运行性能基准 bench

最简用法

python -m fastrnns.bench

README 说明默认输出即是一份不错的全量对比;也可以指定要跑的模型与分组:

python -m fastrnns.bench --rnns cudnn aten jit --group rnns

模型选择与分组(--rnns / --cnns / --group)

如果不传--rnns,bench.py 会使用一组默认 RNN:

cudnn, aten, jit, jit_premul, jit_premul_bias, jit_simple, jit_multilayer, py

不传--cnns时默认 CNN 为resnet18, resnet18_jit, resnet50, resnet50_jit--group默认值是["cnns", "rnns"],二者都跑;--group rnns可只跑 RNN 组(对应 README 的示例)。传入--rnns时请使用注册表里真实存在的名字,否则 runner.py 的get_nn_runners在字典取值时会直接 KeyError。

完整参数表:从 bench.py 入口逐条解析

虽然 README 只示范了--rnns--group,bench.py 的argparseL233-L308)实际已经提供了完整的问题规模与运行控制参数。下表整理了参数、默认值与含义,便于你在真实实验里精确控制:

参数默认值含义
--seqLength100序列时间步长度
--numLayers1LSTM 层数
--inputSize512输入特征维
--hiddenSize512隐层特征维
--miniBatch64batch 大小
--warmup10预热轮数(不计入统计)
--nloops100正式计时的循环轮数
--devicecuda运行设备,传 cpu 会走纯 Pythonperf_counter计时路径
--variable-lstms额外跑变长序列 LSTM(vl_cudnn/vl_jit/vl_py),速度很慢,且该模式忽略--seqLength
--rnns/--cnns见上文指定要跑的模型集合
--groupcnns rnns决定跑哪个大组
--sep空格输出分隔符,可改为,等便于 CSV 化
--print-json不输出oss输出每模型平均耗时 JSON;pep输出逐迭代的 AI-PEPCaffe2Observer记录
--fuserteJIT fuser 后端:te/old/none/default
--executorNoneJIT executor:legacy/simple/profiling/default
--cuda-pointwise-loop-levelNone透传设置 TensorExpr(te)fuser 的 CUDA pointwise 循环/分块参数

需要特别说明的是模型规模会直接影响耗时数量级与可运行性:默认单层 100 步 × batch 64 的 cuDNN LSTM 在普通 GPU 上很快,而变长 LSTM 或py(纯 Python 逐时间步循环)在默认 100 轮迭代下可能非常慢。

计时口径与输出解读

每次迭代train_batch(bench.pyL84-L130)的顺序是:gc.collect()→ 用 CUDA event 记录 forward 时间(包在record_function("## forward ##")中,供 profiler 观测)→ 构造反向输入(默认对 hidden 取随机 grad_output)→ 记录 backward 时间 → 校验每个参数都有梯度并grad.zero_()torch.cuda.synchronize()后取 event 差值。

结果以BenchResultnamedtuple 呈现,字段包括:nameavg_fwd/std_fwd/info_fwd(前向均值/标准差/全部原始迭代数据)、avg_bwd/std_bwd/info_bwd(反向对应值)。控制台输出为表头 + 每模型一行;--print-json oss时 stdout 只会输出 JSON(形如{组名: {模型: avg 耗时}},同时按组名-backward输出反向数据),配合脚本化采集非常方便。

每个模型在测前会执行若干轮 warmup(默认 10),正式计时nloops(默认 100)轮后取均值与标准差——标准差大说明该实现/该环境抖动明显。若某模型运行抛出异常且处于 JSON 输出模式,会被静默跳过(便于部分模型不支持的场景继续跑完)。

进阶:变长序列 LSTM(variable-length LSTM)基准

batch 内各样本长度不一致的 RNN 是 NLP 场景的常态,fastrnns 通过--variable-lstms支持。运行:

python -m fastrnns.bench --variable-lstms

该选项会追加vl_cudnn(原生pack_sequence路径)、vl_jit(脚本化变长实现)、vl_py(纯 Python 变长实现)三个 runner 的基准(bench.pyL324L343-L351)。数据由varlen_lstm_inputs随机生成:每个样本长度在minlen=30maxlen=100间随机取值,再做 padding/packing。源码注释特别警告:部分变长实现极不优化,跑起来非常慢,因此在warmup + nloops超过 30 时入口会向 stderr 打印警告,提示这类 LSTM 运行时间很长。若只是复现 cuDNN 变长基准可只跑vl_cudnn

第三步:用 nvprof 剖析模型(profile)

命令

python -m fastrnns.profile

README 说明这会为所有模型在某个位置生成 nvprof 文件;也可只剖析指定模型:

python -m fastrnns.profile --rnns aten jit

入口不传--rnns时默认剖析["cudnn", "aten", "jit"](profile.pyL165-L166)。

内部机制:一次"命令包装命令"

nvprof 剖析 的实现方式是递归地把自己再跑一遍:不传--internal-run时(外层),脚本会用subprocess执行nvprof -o <outpath> python -m fastrnns.profile ... --rnns <names> --internal-run;内层--internal-run模式下才真正实例化模型并循环跑若干次 forward + backward(默认nloops=5,每轮之间sleep_between_seconds=1,便于让 nvprof 的采样/事件捕获落在干净的区间内)。若外层 nvprof 返回码非 0,会抛 RuntimeError 并附带 stdout/stderr 以便排查。

输出的.nvvp文件落在~/profout/(源码中OUTPUT_DIR,注意它是 shell~而非字面路径),文件名由nvprof_output_filename拼接而成,格式为:

prof_{模型列表用-连接}_{s序列长-l层数-i输入-h隐藏-bbatch}_{MMDDYY-HHMM}.nvvp

例如单层默认尺寸下剖析atenjit,会得到类似prof_aten-jit_s100-l1-i512-h512-b64_090618-1030.nvvp的文件。用 NVIDIA Nsight / nvvp GUI 打开即可看到每个 kernel 的耗时分布。注意nvprof是 CUDA 自带的命令行 profiler,若没有安装对应版本的工具链,该命令会直接失败;这也是 README"很多测试只在 CUDA 上运行"最典型的体现。

源码级机制:理解基准为何可信、可扩展

这一节把上面命令背后的三层设计拆开,它们是整个 fastrnns 可复用性的基石。

1. ModelDef:把"模型"抽象成五个可调用的部件

factory.py 顶部定义了统一接口约定:一个 creator 是(options) -> ModelDef,其中ModelDef = (inputs, params, forward, backward_setup, backward)L35-L37):

  • inputs:可直接forward(*inputs)调用;
  • params:所有requires_grad=True的参数列表;
  • forward:模块/图执行器/函数;
  • backward_setup(outputs) -> backward_inputs:把 forward 输出转成反向所需的梯度入口(默认实现用torch.randn_like造随机梯度);
  • backward:真正执行反向传播。

于是 bench.py 的计时循环只依赖这套通用接口,完全不知道被测对象是 cuDNN、脚本化循环还是 ResNet。pytorch_lstm_creator直接包装torch.nn.LSTM模块;lstm_creator(script=True)则用 cells.py 的手写 cell(gates = mm(input, w_ih.t()) + mm(hx, w_hh.t()) + b_ih + b_hhchunk(4)后过 sigmoid/tanh 门控)拼出逐时间步循环,再交给torch.jit.script编译。jit_premul/jit_premul_bias的差异在于把input × w_ih提到循环外一次性完成(premul_bias还用一次mm+ bias 广播代替逐步加 bias,源码注释指出这能让 backward 合并成更少的 reduction kernel)。

2. runner 注册表与 Context:如何公平地切换后端

runner.py 用nn_runners字典统一注册所有模型,每个条目带一个context(上下文管理器)。它解决了一个公平性问题:被测实现不同,所需的全局状态也不同——例如测aten时必须临时关闭 cuDNNDisableCuDNN进入时保存torch.backends.cudnn.enabled、退出时恢复),否则torch.nn.LSTM仍然会落到 cuDNN 路径上,测不出纯 ATen 性能;而pyrunner 在启动时若发现PYTORCH_JIT环境变量为真,AssertNoJIT会直接拒绝运行,确保纯 Python 实现不被 JIT 干扰。bench/test/profile 三处入口统一用with context():包裹再创建与运行模型,保证各后端在各自"应有"的配置下被测量。

3. fuser 与 executor:影响 JIT 性能的两大开关

bench.py 的参数--fuser默认te,经 fuser.py 的set_fuser落到一组torch._C._jit_*内部开关上:

  • te:开启 profiling executor 与算子融合优化、允许 GPU 上融合、启用 TensorExpr fuser;
  • old:关闭 profiling executor 与图优化,GPU 融合可用但禁用 TensorExpr fuser;
  • none:禁用 profiling executor、图优化与 CPU/GPU 融合(最接近"不优化"的参照);
  • default:不做任何覆盖。

--executorprofiling/simple/legacy/default)会进一步覆盖 executor 级设置。这两个参数正是为了回答"性能差异到底来自 fuser 还是算子实现"这类问题而存在。

4. pytest 集成(可选运行方式)

除独立脚本外,conftest.py 与 test_bench.py 提供了一套 pytest-benchmark 驱动的运行方式:pytest会按net_name × executor × fuser参数化出TestBenchNetwork测试,每个网络测量test_forward/test_backward两件事。conftest.py为 pytest 增加了--fuser(默认old)与--executor(默认legacy)两个选项。这样 fastrnns 既可以独立跑,也可以纳入仓库统一的 pytest 基准流程(与 pytest-benchmark 的group="fastrnns"分组对齐)。

常见问题与建议

  • 在哪个目录下运行?README 的全部命令都以python -m fastrnns.xxx形式出现,说明需要把benchmarks/加入模块搜索路径(在benchmarks目录内执行即可);若在仓库根目录执行会遇到模块找不到的问题。
  • 没有 GPU 能跑吗?部分能。bench.py --device cpu走纯perf_counter计时(bench.pyL57-L68为 CUDA Event 的 CPU shim),但 cuDNN、变长、nvprof 剖析等大量路径仍要求 CUDA 环境;test.pyprofile.py更是直接要求device=cuda。README 的 Caveats 与源码一致。
  • 结果抖动大?优先落实 README 的三项环境措施:CPU performance governor、关闭 Turbo、cset shield隔离核心;其次检查 warmup 轮数是否充足(--warmup),并关注输出中的std字段。
  • 如何自动化采集?使用--print-json oss(每模型平均耗时)或--print-json pep(逐迭代数据)把 stdout 重定向到文件即可,注意此时模型异常会被静默跳过而非抛错中断。

fastrnns 的价值不仅在于给出若干条现成命令,更在于它示范了一套可复用的基准方法论:用统一的问题规模与参数语义约束所有实现,用 ModelDef 把前向/反向/梯度清零流程协议化,用 runner context 管理不同后端的运行前提,最后以 cuDNN 原生实现作为数值一致性的对照锚点。参考它的做法,你可以轻松把新的 RNN 变体(如 Layernorm LSTM、带 dropout 的 LSTM,甚至非循环结构的 ResNet)注册进 runner.py 的nn_runners,纳入同一套正确性校验与计时体系。

【免费下载链接】pytorchTensors and Dynamic neural networks in Python with strong GPU acceleration项目地址: https://gitcode.com/GitHub_Trending/py/pytorch

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询