[pytorch]使用 CUDA 10 Graphs API 的静态图

2024-03-20 304 views
2
?特征

CUDA 10 发布了一项名为 CUDA Graphs 的新功能,它允许您构建静态图,从而最大限度地减少启动多个内核的开销。该 API 附带的函数允许您捕获流(也支持多个流)并将其转换为 CUDA 图形。将此功能暴露给 pytorch 对于许多应用程序来说非常有益。

动机

当使用小内核时,在不增加复杂性的情况下最小化任何开销非常重要。

沥青

我提出类似的建议:

g = CUDAGraph()
with g.capture():
    # Static computation that can be combined into 1 graph
    ....

while True:
    g.execute()
备择方案

目前 Pytorch 提供 JIT 和 C++ API 来减轻开销。CUDA Graph 方法理论上更快,而且恕我直言,对用户更友好。

额外的背景信息

我有一个小型演示,我运行了一个小型神经网络 1000 次,以下是我当前的基准:

Pytorch c++ Front-end: 152.633ms
Pytorch c++ with cuda graphs capture:136.692ms
Pytorch Python: 354.79ms
Pytorch Python + cuda graphs capture: 134.29ms

抄送@ngimel

回答

6

抄送:@ngimel @csarofeen

我们中的一些人一直在讨论如何使用 CUDA TaskGraph API 来提高效率,但我们还没有真正收敛到任何东西。

不过,大部分讨论都是围绕将其集成到 JIT 中的。我不确定引入另一个不是 torch.jit.trace 或 torchscript 的图形 API 有何感受,它看起来像是另一个图形,但只能在 CUDA10+ 上的有限设置中工作。

此外,使用不同的张量/指针重新执行相同的图将涉及 O(N) 次调用cudaGraphKernelNodeSetParams(我猜?),而且我不确定这有多昂贵。

5

另抄送:@zheng-xq

3

CUDA 10 中图形的两个最大问题是 1) 内核参数被捕获且无法更改,这意味着每次重播将使用相同的内存指针。但是 pytorch 缓存分配器不保证使用相同的指针有效 2)不支持捕获的流的任何同步https://docs.nvidia.com/cuda/cuda-c-programming-guide/index。 html#prohibited-unhandled-operations。这意味着对于许多工作负载来说,捕获将不成功,因为许多 pytorch 操作在内部同步(特别是调用推力排序的任何操作,其中包括用于索引操作和嵌入的向后操作,以及常规排序或任何同步内存副本)

6

你是对的,我们需要强制图形使用的所有指针与图形一样长。我计划拦截分配请求,以便我们能够做出这些保证(不能 100% 确定需要做多少工作才能实现这一点)。

确实,此功能将针对具有静态执行/内存指针而无需中间执行同步的应用程序(无论如何都应该避免,不是吗?)。某些 Pytorch 功能根本不受支持。

8

这看起来超级酷,想知道@fps7806是否有任何更新我很好奇,你是如何cuda捕获C++ pytorch操作的?提前致谢

莱科宁

0
48875 最近被合并,您可以查看基本 cuda 图捕获和重放的测试(需要 cuda 11)。如果你胆子大,可以尝试玩一下。正如测试中所指出的,我们目前不执行 cuda 图正常运行所需的任何内存管理,因此用户有责任确保图所需的内存不会被重用于其他用途。
1

这太酷了!你们有支持的操作列表吗

向后进行索引操作和嵌入,以及常规排序或任何同步内存副本)

即现在支持这些吗?

2

不幸的是,不,因为这些仍然需要主机设备同步,所以不支持它们。

4

我明白了,感谢 ngimel 的解释!:)

1

@ngimel 只是为了确认这些是 Cuda Graph 不支持的唯一限制/操作吗?

3

差远了。我们还不知道。

8

我明白了,我明白了,我会在开发过程中提出一些问题,然后遇到问题。谢谢你们:)

1

如果我是你,我至少会等待图表变得分配器安全,否则你会在任何实质性用例中遇到奇怪的不确定性错误。现在https://github.com/pytorch/pytorch/pull/48875已合并,我将立即开始处理分配器更改。

0

@mcarilli 感谢您为此所做的工作!这是非常酷的东西。我能够编译您的合并请求#51075并尝试捕获整个 resnet 图。我能够验证前向传递是否已正确捕获/启动!很棒的东西。不幸的是,无法捕获向后的道具,但我猜这是一个已知问题?它似乎不尊重 cuda 流。我尝试过在相同和单独的流上运行它,并且全部在一张图或两张图(向前/向后)中运行。

  File "/home/ubuntu/anaconda3/lib/python3.8/site-packages/torch/tensor.py", line 245, in backward
    torch.autograd.backward(self, gradient, retain_graph, create_graph, inputs=inputs)
  File "/home/ubuntu/anaconda3/lib/python3.8/site-packages/torch/autograd/__init__.py", line 145, in backward
    Variable._execution_engine.run_backward(
RuntimeError: CUDA error: operation would make the legacy stream depend on a capturing blocking stream
[W CUDAGraph.cpp:197] Warning: CUDA warning: operation not permitted when stream is capturing (function reset)

有没有想过如何避免这个问题?我在想我可能只需要实现向后传递,而不是依赖 Autograd?这可能有效吗?

谢谢!

3

@bhetherman

我能够编译您的合并请求 #51075 并尝试捕获整个 resnet 图。我能够验证前向传递是否已正确捕获/启动!

你是一个勇敢的灵魂。但听到这个消息真是太好了(而且让人松了口气)。

确保在捕获之前运行几次预热迭代,尤其是当您设置torch.backends.cudnn.benchmark = True. 您不希望所有 cudnn 基准测试空运行内核都以捕获结束。

我在想我可能只需要实现向后传递,而不是依赖 Autograd?这可能有效吗?

从理论上讲,当然可以……但请不要陷入这个麻烦。

向后传递失败是半已知的,也是我接下来要研究的事情之一。我怀疑正是这一行告诉默认流在向后结束时与向后操作同步。export TORCH_SHOW_CPP_STACKTRACES=1您可以在运行脚本之前尝试进行检查。

在那个位置,我们也许能够告诉环境流(也称为您设置用于捕获图形的用户指定的流)与向后操作同步。我需要与@mruberry @albanD @ngimel 一起审查。

与此同时,如果您想自行承担测试的风险,如果您的捕获仅使用一个流,您可能只需注释掉该行,向后捕获就应该成功。您必须在捕获之前和之后将捕获流与脚本的周围流同步,即

capture_stream.wait_stream(torch.cuda.current_stream())
with torch.cuda.stream(capture_stream):
    capture fwd and bwd...
torch.cuda.current_stream().wait_stream(capture_stream)

但这无论您要捕获什么(无论是否涉及向后传递)都是至关重要的。

自我注释:Pytorch 在本地线程设置流。ifdefault_stream.wait(event);是从后向线程(而不是主/前向)线程调用的,它可能是主线程的当前流,而不是调用侧线程的当前流,我们需要检索和交换default_stream.

1

后向工作人员在运行用户代码 IIRC 之前正确更新其流。但我认为你比我更了解同步在引擎中是如何工作的:D

9

@mcarilli 感谢您的指点!

“自己实现反向传播”的想法只是为了进行概念证明。如果需要的话,真的不想做任何比简单的密集网络或单个卷积模型更多的事情。绝对不是A选项哈哈。

这是导出 TORCH_SHOW_CPP_STACKTRACES=1 的堆栈跟踪

    local_loss.backward()
  File "/home/ubuntu/anaconda3/lib/python3.8/site-packages/torch/tensor.py", line 245, in backward
    torch.autograd.backward(self, gradient, retain_graph, create_graph, inputs=inputs)
  File "/home/ubuntu/anaconda3/lib/python3.8/site-packages/torch/autograd/__init__.py", line 145, in backward
    Variable._execution_engine.run_backward(
RuntimeError: CUDA error: operation would make the legacy stream depend on a capturing blocking stream
Exception raised from block at ../c10/cuda/impl/CUDAGuardImpl.h:150 (most recent call first):
frame #0: c10::Error::Error(c10::SourceLocation, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >) + 0x6c (0x7f25d368cddc in /home/ubuntu/anaconda3/lib/python3.8/site-packages/torch/lib/libc10.so)
frame #1: c10::detail::torchCheckFail(char const*, char const*, unsigned int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) + 0xfa (0x7f25d365a1f4 in /home/ubuntu/anaconda3/lib/python3.8/site-packages/torch/lib/libc10.so)
frame #2: <unknown function> + 0xb7ce (0x7f25d36bd7ce in /home/ubuntu/anaconda3/lib/python3.8/site-packages/torch/lib/libc10_cuda.so)
frame #3: <unknown function> + 0x362c437 (0x7f261d5bc437 in /home/ubuntu/anaconda3/lib/python3.8/site-packages/torch/lib/libtorch_cpu.so)
frame #4: <unknown function> + 0x362ec6f (0x7f261d5bec6f in /home/ubuntu/anaconda3/lib/python3.8/site-packages/torch/lib/libtorch_cpu.so)
frame #5: torch::autograd::Engine::thread_main(std::shared_ptr<torch::autograd::GraphTask> const&) + 0x23a (0x7f261d5c47ea in /home/ubuntu/anaconda3/lib/python3.8/site-packages/torch/lib/libtorch_cpu.so)
frame #6: torch::autograd::Engine::thread_init(int, std::shared_ptr<torch::autograd::ReadyQueue> const&, bool) + 0x9f (0x7f261d5bccdf in /home/ubuntu/anaconda3/lib/python3.8/site-packages/torch/lib/libtorch_cpu.so)
frame #7: torch::autograd::python::PythonEngine::thread_init(int, std::shared_ptr<torch::autograd::ReadyQueue> const&, bool) + 0x67 (0x7f262376baf7 in /home/ubuntu/anaconda3/lib/python3.8/site-packages/torch/lib/libtorch_python.so)
frame #8: <unknown function> + 0xd6d84 (0x7f262fe17d84 in /lib/x86_64-linux-gnu/libstdc++.so.6)
frame #9: <unknown function> + 0x9609 (0x7f26500cc609 in /lib/x86_64-linux-gnu/libpthread.so.0)
frame #10: clone + 0x43 (0x7f264fff3293 in /lib/x86_64-linux-gnu/libc.so.6)

我希望这有帮助。我应该在下周的某个时间向您提供有关注释掉该行并尝试手动同步流的建议。我会让你知道进展如何。目前,我在捕获之前没有进行任何手动同步,而是在执行“torch.cuda.synchronize()”来同步整个设备之后。听起来这可能有点矫枉过正,但确实完成了工作。谢谢!

编辑:刚刚注意到你对热身跑的评论。我正在做一些,但至少我需要做一个,因为如果我不这样做,就会出错CUDNN_STATUS_ALLOC_FAILED

0

您的 Pytorch 不是使用调试标志构建的,但错误来自 autograd 引擎中的某个位置,因此我们可能走在正确的轨道上。最简单的方法是注释掉该行,重新编译,然后看看会发生什么。

torch.cuda.synchronize()即使您注释掉该行,捕获工作之前和之后也是如此default_stream.wait(event);

9

是的,抱歉,我想提一下我没有调试标头。当我尝试你的建议时,我将在下周重建并启用它们。

2

@mcarilli 嘿,你是对的!注释掉 engine.cpp 中的该行可以让向后传递工作!我可以使用图形 API 端到端训练 resnet18 模型!我将https://pytorch.org/tutorials/beginner/transfer_learning_tutorial.html转换为使用该 API,并验证它的训练精度是否相似。

在调试标志上,我不确定我做错了什么。我使用 env DEBUG=1 重新构建,我可以看到它的构建和安装 pytorch 的调试配置,但堆栈跟踪不再可读。所以不确定那里到底出了什么问题。

...
Building wheel torch-1.9.0a0+10f4eb1
-- Building version 1.9.0a0+10f4eb1
cmake --build . --target install --config Debug -- -j 8
[1/2] Install the project...
-- Install configuration: "Debug"
...
6

@bhetherman 分配器图安全现已合并https://github.com/pytorch/pytorch/pull/54038(需要重新提交以改进一些片状测试)。

https://github.com/pytorch/pytorch/pull/54227仍在讨论中,是计划的两部分中的第一个,用于删除您需要注释掉的默认流同步,并将其替换为当前的同步溪流。然后

capture_stream.wait_stream(torch.cuda.current_stream())
with torch.cuda.stream(capture_stream):
    capture fwd and bwd...
torch.cuda.current_stream().wait_stream(capture_stream)

应该可以工作,只要您维护图形的静态输入和输出缓冲区(如果您的 resnet18 示例有效,您一定已经以某种方式这样做了)。

5

您好,只是想查询 PyTorch 现在是否支持 CUDA 图,还是还需要一些时间?

6

关闭此问题作为它添加的支持,对于其他功能和改进,我们应该打开单独的跟踪问题。

5

@mcarilli 感谢您支持 CUDA 图。我想知道当前的实现是否支持 torchscript 和 profiler?

7

CUDA 图避免了所有 CPU 方面的工作,因此分析 cuda 图的重放不会产生任何有用的信息。如果模型是图形区域和非图形区域的混合,则仍可以照常对非图形区域进行分析。至于torchScript,您可以捕获脚本化模型,但捕获仍然会受到cuda graph捕获限制(例如没有同步,没有控制流,没有数据依赖的输出大小),所以如果脚本化模型不满足这些,它将默默地产生错误的结果。

9

谢谢@ngimel!在对跟踪代码进行更多预热后,我使用 cuda graph 解决了 torchscript 问题。对于 pytorch 分析,我想使用/不使用 cuda 图来分析更多细节。g.replay()不幸的是,如果我放在探查器上下文下,探查器就会挂在那里。nsys可以很好地与cuda graph配合使用。我会做更多调查,如果发现更多,请告诉你。

7

@ngimel 快速更新:将所有内容都放在分析器上下文中后,分析效果很好;但如果将图形的创建和捕获置于探查器上下文之外,则在退出探查器上下文时,分析将挂 在此处。还不确定根本原因。

7

感谢更新!抄送 @gdankel 来查看挂起的探查器