178 lines
6.4 KiB
Python
178 lines
6.4 KiB
Python
import gc
|
|
from typing import Literal
|
|
from typing_extensions import Self
|
|
|
|
import torch
|
|
from torch._C import _acceleratorGraph
|
|
|
|
|
|
class Graph(_acceleratorGraph):
|
|
r"""
|
|
Wrapper around an :ref:`accelerator<accelerators>` graph that supports capture and replay.
|
|
|
|
A graph captures a sequence of operations and their dependencies, allowing them to be
|
|
replayed efficiently with reduced overhead. This class can be used as a context manager
|
|
to automatically capture operations on the current stream.
|
|
|
|
Arguments:
|
|
keep_graph (bool, optional): If ``False``, the underlying graph is destroyed and the
|
|
executable graph is instantiated on the GPU at the end of ``capture_end``.
|
|
If ``True``, the underlying graph is preserved after ``capture_end``. In this case,
|
|
the executable graph is not instantiated automatically; it must be explicitly created
|
|
by calling ``instantiate``, or it will be instantiated on the first call to ``replay``.
|
|
Defaults to ``False``.
|
|
pool (tuple[int, int], optional): Memory pool identifier for this graph. Multiple graphs
|
|
can share the same pool by passing the same identifier, which can reduce memory overhead.
|
|
Defaults to ``None``.
|
|
capture_error_mode (Literal["default", "global", "thread_local", "relaxed"], optional):
|
|
Specifies the behavior of graph capture. The exact semantics are backend-specific.
|
|
``"default"``: backend-defined default capture behavior.
|
|
``"global"``: potentially unsafe API calls are prohibited. Errors may occur if capture
|
|
in the current thread affects other threads.
|
|
``"thread_local"``: potentially unsafe API calls are prohibited. Errors occur only if
|
|
capture in the current thread affects itself.
|
|
``"relaxed"``: the current thread is allowed to make potentially unsafe API calls, except
|
|
for calls that inherently conflict with stream capture.
|
|
Default: ``"default"``.
|
|
|
|
Example::
|
|
|
|
>>> # xdoctest: +SKIP
|
|
>>> x = torch.zeros([2000], device=0)
|
|
|
|
>>> stream = torch.Stream()
|
|
>>> graph = torch.accelerator.Graph()
|
|
>>> with stream, graph:
|
|
... x += 1
|
|
|
|
>>> graph.replay()
|
|
"""
|
|
|
|
def __new__(
|
|
cls,
|
|
keep_graph: bool = False,
|
|
*,
|
|
pool: tuple[int, int] | None = None,
|
|
capture_error_mode: Literal[
|
|
"default", "global", "thread_local", "relaxed"
|
|
] = "default",
|
|
) -> Self:
|
|
return super().__new__(cls, keep_graph)
|
|
|
|
def __init__(
|
|
self,
|
|
keep_graph: bool = False,
|
|
*,
|
|
pool: tuple[int, int] | None = None,
|
|
capture_error_mode: Literal[
|
|
"default", "global", "thread_local", "relaxed"
|
|
] = "default",
|
|
) -> None:
|
|
super().__init__(keep_graph)
|
|
self.graph_pool = pool
|
|
self.capture_error_mode = capture_error_mode
|
|
|
|
# pyrefly: ignore [bad-override]
|
|
def capture_begin(self) -> None:
|
|
r"""
|
|
Begin graph capture on the current stream.
|
|
|
|
All operations on the current stream after this call will be recorded into the graph until
|
|
``capture_end`` is called, using the memory pool and capture error mode provided at construction time.
|
|
"""
|
|
super().capture_begin(
|
|
pool=self.graph_pool, capture_error_mode=self.capture_error_mode
|
|
)
|
|
|
|
def capture_end(self) -> None:
|
|
r"""
|
|
End graph capture on the current stream of the current device.
|
|
|
|
After this call, the graph can be replayed via ``replay``.
|
|
"""
|
|
super().capture_end()
|
|
|
|
def instantiate(self) -> None:
|
|
r"""
|
|
Instantiate the underlying graph. Will be called by ``capture_end``
|
|
if ``keep_graph=False``, or by ``replay`` if ``keep_graph=True`` and
|
|
``instantiate`` has not already been explicitly called.
|
|
"""
|
|
super().instantiate()
|
|
|
|
def replay(self) -> None:
|
|
r"""Replay the work captured by this graph."""
|
|
super().replay()
|
|
|
|
def reset(self) -> None:
|
|
r"""
|
|
Delete the graph currently held by this instance.
|
|
|
|
After this call, the graph can be recaptured. Set :attr:`graph_pool` or
|
|
:attr:`capture_error_mode` beforehand to use different settings on the next capture.
|
|
"""
|
|
super().reset()
|
|
|
|
def pool(self) -> tuple[int, int]:
|
|
r"""
|
|
Return an opaque token representing the id of this graph's memory pool.
|
|
|
|
This id can optionally be passed to another graph's ``capture_begin``,
|
|
which hints the other graph may share the same memory pool.
|
|
|
|
Example::
|
|
>>> # xdoctest: +SKIP
|
|
>>> g1 = torch.accelerator.Graph()
|
|
>>> g1.capture_begin()
|
|
>>> # ... operations ...
|
|
>>> g1.capture_end()
|
|
|
|
>>> # Share g1's memory pool with a new graph
|
|
>>> pool_id = g1.pool()
|
|
>>> g2 = torch.accelerator.Graph(pool=pool_id)
|
|
"""
|
|
return super().pool()
|
|
|
|
def enable_debug_mode(self) -> None:
|
|
r"""Enable debugging mode for ``debug_dump``."""
|
|
return super().enable_debug_mode()
|
|
|
|
def debug_dump(self, path: str) -> None:
|
|
r"""
|
|
Dump the captured graph to a file for debugging purposes if the debugging is
|
|
enabled via ``enable_debug_mode``.
|
|
|
|
Arguments:
|
|
path (str): Path to dump the graph to.
|
|
|
|
Example::
|
|
>>> # xdoctest: +SKIP
|
|
>>> s = torch.Stream()
|
|
>>> g = torch.accelerator.Graph()
|
|
>>> g.enable_debug_mode()
|
|
|
|
>>> with s, g:
|
|
>>> # ... operations ...
|
|
|
|
>>> # Dump captured graph to a file "graph_dump.dot"
|
|
>>> g.debug_dump("graph_dump.dot")
|
|
"""
|
|
return super().debug_dump(path)
|
|
|
|
def __enter__(self) -> None:
|
|
torch.accelerator.synchronize()
|
|
if torch.compiler.config.force_cudagraph_gc:
|
|
# We previously always ran garbage collection here. While this can help
|
|
# reclaim accelerator device memory held by dead Python cycles, it is
|
|
# very expensive, especially when performing multiple graph captures in sequence.
|
|
gc.collect()
|
|
torch.accelerator.empty_cache()
|
|
torch.accelerator.empty_host_cache()
|
|
self.capture_begin()
|
|
|
|
def __exit__(self, *exc_info: object) -> None:
|
|
self.capture_end()
|
|
|
|
|
|
__all__ = ["Graph"]
|