torch.Tensor.record_stream#
- Tensor.record_stream(stream)#
标记张量已被此流使用。当张量被释放时,请确保在释放时此流上排队的所有工作都已完成之前,张量内存不会被另一个张量重用。
注意
缓存分配器仅知道张量分配所在的流。由于这种意识,它已经正确地管理了单个流上张量的生命周期。但是,如果张量在一个与其原始流不同的流上使用,分配器可能会意外地重用内存。调用此方法可以告知分配器哪些流使用了该张量。
警告
此方法最适合以下用例:您提供了一个在辅助流上创建张量的函数,并且希望用户能够使用该张量,而无需在首次使用张量时过多地考虑流安全。这些安全保证会带来一定的性能和可预测性成本(类似于垃圾回收和手动内存管理之间的权衡),因此,如果您能够完全管理张量的生命周期,则可以考虑手动管理 CUDA 事件,这样就不需要调用此方法。特别地,当您调用此方法时,在稍后的分配中,分配器将轮询记录的流以查看所有操作是否已完成;您可能会与辅助流计算发生竞争,并非确定性地重用或未能重用分配内存。
您可以安全地使用在辅助流上分配的张量,而无需
record_stream()
;您必须手动确保在释放张量之前,将任何非创建流对张量的使用同步回创建流。由于 CUDA 缓存分配器保证内存仅会与相同的创建流重用,因此这足以确保未来对内存的重新分配的写入将被延迟,直到非创建流的使用完成。(反直觉的是,您可能会注意到在 CPU 端我们已经重新分配了张量,即使旧张量上的 CUDA 内核仍在进行中。这没关系,因为新张量上的 CUDA 操作将适当地等待旧操作完成,因为它们都在同一个流上。)具体来说,这看起来像这样
with torch.cuda.stream(s0): x = torch.zeros(N) s1.wait_stream(s0) with torch.cuda.stream(s1): y = some_comm_op(x) ... some compute on s0 ... # synchronize creation stream s0 to side stream s1 # before deallocating x s0.wait_stream(s1) del x
请注意,在决定何时执行
s0.wait_stream(s1)
时需要一定的判断。特别地,如果我们立即在some_comm_op
之后等待,那么设置辅助流就没有意义了;这相当于在s0
上运行some_comm_op
。相反,同步必须放置在某个适当的、稍后的时间点,您期望辅助流s1
已经完成了工作。这个位置通常通过分析来确定,例如,使用 Chrome 跟踪(通过torch.autograd.profiler.profile.export_chrome_trace()
生成)。如果您过早地放置等待,s0 上的工作将一直阻塞,直到s1
完成,从而阻止进一步的通信和计算重叠。如果您过晚地放置等待,您将使用比严格必需更多的内存(因为您将x
保持活动状态的时间更长)。有关如何在实践中应用此指导的具体示例,请参阅此帖子:FSDP 和 CUDACachingAllocator。