Initial import: grid-bot — grid trading bot for BTC-USDT on Cifra Markets
This commit is contained in:
@@ -0,0 +1,650 @@
|
||||
# mypy: allow-untyped-defs
|
||||
r"""
|
||||
This package introduces support for the XPU backend, specifically tailored for
|
||||
Intel GPU optimization.
|
||||
|
||||
This package is lazily initialized, so you can always import it, and use
|
||||
:func:`is_available()` to determine if your system supports XPU.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
import traceback
|
||||
from functools import lru_cache
|
||||
from typing import Any, NewType, TYPE_CHECKING
|
||||
|
||||
import torch
|
||||
import torch._C
|
||||
from torch._utils import _dummy_type, _LazySeedTracker
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
from torch.types import Device
|
||||
|
||||
from ._utils import _get_device_index
|
||||
from .graphs import (
|
||||
graph,
|
||||
graph_pool_handle,
|
||||
is_current_stream_capturing,
|
||||
make_graphed_callables,
|
||||
XPUGraph,
|
||||
)
|
||||
from .streams import Event, Stream
|
||||
|
||||
|
||||
_initialized = False
|
||||
_tls = threading.local()
|
||||
_initialization_lock = threading.Lock()
|
||||
_queued_calls: list[
|
||||
tuple[Callable[[], None], list[str]]
|
||||
] = [] # don't invoke these until initialization occurs
|
||||
_is_in_bad_fork = getattr(torch._C, "_xpu_isInBadFork", lambda: False)
|
||||
_lazy_seed_tracker = _LazySeedTracker()
|
||||
default_generators: tuple[torch._C.Generator] = () # type: ignore[assignment]
|
||||
|
||||
|
||||
def _is_compiled() -> bool:
|
||||
r"""Return true if compile with XPU support."""
|
||||
return torch._C._has_xpu
|
||||
|
||||
|
||||
if _is_compiled():
|
||||
_XpuDeviceProperties = torch._C._XpuDeviceProperties
|
||||
_exchange_device = torch._C._xpu_exchangeDevice
|
||||
_maybe_exchange_device = torch._C._xpu_maybeExchangeDevice
|
||||
else:
|
||||
# Define dummy if PyTorch was compiled without XPU
|
||||
_XpuDeviceProperties = _dummy_type("_XpuDeviceProperties") # type: ignore[assignment, misc]
|
||||
|
||||
def _exchange_device(device: int) -> int:
|
||||
raise NotImplementedError("PyTorch was compiled without XPU support")
|
||||
|
||||
def _maybe_exchange_device(device: int) -> int:
|
||||
raise NotImplementedError("PyTorch was compiled without XPU support")
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def device_count() -> int:
|
||||
r"""Return the number of XPU device available."""
|
||||
if not _is_compiled():
|
||||
return 0
|
||||
return torch._C._xpu_getDeviceCount()
|
||||
|
||||
|
||||
def is_available() -> bool:
|
||||
r"""Return a bool indicating if XPU is currently available."""
|
||||
# This function never throws.
|
||||
return device_count() > 0
|
||||
|
||||
|
||||
def is_bf16_supported(including_emulation: bool = True) -> bool:
|
||||
r"""Return a bool indicating if the current XPU device supports dtype bfloat16."""
|
||||
if not is_available():
|
||||
return False
|
||||
return (
|
||||
including_emulation
|
||||
or torch.xpu.get_device_properties().has_bfloat16_conversions
|
||||
)
|
||||
|
||||
|
||||
def is_tf32_supported() -> bool:
|
||||
r"""Return a bool indicating if the current XPU device supports dtype tf32."""
|
||||
if not is_available():
|
||||
return False
|
||||
# On Intel Xe architecture and newer, TF32 operations can be accelerated
|
||||
# through DPAS (Dot Product Accumulate Systolic) instructions. Therefore,
|
||||
# TF32 support can be determined by checking whether the device supports
|
||||
# subgroup matrix multiply-accumulate operations.
|
||||
return torch.xpu.get_device_properties().has_subgroup_matrix_multiply_accumulate
|
||||
|
||||
|
||||
def is_initialized():
|
||||
r"""Return whether PyTorch's XPU state has been initialized."""
|
||||
return _initialized and not _is_in_bad_fork()
|
||||
|
||||
|
||||
def _lazy_call(callable, **kwargs) -> None:
|
||||
if is_initialized():
|
||||
callable()
|
||||
else:
|
||||
global _lazy_seed_tracker
|
||||
if kwargs.get("seed_all", False):
|
||||
_lazy_seed_tracker.queue_seed_all(callable, traceback.format_stack())
|
||||
elif kwargs.get("seed", False):
|
||||
_lazy_seed_tracker.queue_seed(callable, traceback.format_stack())
|
||||
else:
|
||||
# Don't store the actual traceback to avoid memory cycle
|
||||
_queued_calls.append((callable, traceback.format_stack()))
|
||||
|
||||
|
||||
def init() -> None:
|
||||
r"""Initialize PyTorch's XPU state.
|
||||
This is a Python API about lazy initialization that avoids initializing
|
||||
XPU until the first time it is accessed. Does nothing if the XPU state is
|
||||
already initialized.
|
||||
"""
|
||||
_lazy_init()
|
||||
|
||||
|
||||
def _lazy_init() -> None:
|
||||
global _initialized, _queued_calls
|
||||
if is_initialized() or hasattr(_tls, "is_initializing"):
|
||||
return
|
||||
with _initialization_lock:
|
||||
# This test was was protected via GIL. Double-check whether XPU has
|
||||
# already been initialized.
|
||||
if is_initialized():
|
||||
return
|
||||
# Stop promptly upon encountering a bad fork error.
|
||||
if _is_in_bad_fork():
|
||||
raise RuntimeError(
|
||||
"Cannot re-initialize XPU in forked subprocess. To use XPU with "
|
||||
"multiprocessing, you must use the 'spawn' start method"
|
||||
)
|
||||
if not _is_compiled():
|
||||
raise AssertionError("Torch not compiled with XPU enabled")
|
||||
# This function inits XPU backend and detects bad fork processing.
|
||||
torch._C._xpu_init()
|
||||
# Some of the queued calls may reentrantly call _lazy_init(); We need to
|
||||
# just return without initializing in that case.
|
||||
_tls.is_initializing = True
|
||||
|
||||
_queued_calls.extend(calls for calls in _lazy_seed_tracker.get_calls() if calls)
|
||||
|
||||
try:
|
||||
for queued_call, orig_traceback in _queued_calls:
|
||||
try:
|
||||
queued_call()
|
||||
except Exception as e:
|
||||
msg = (
|
||||
f"XPU call failed lazily at initialization with error: {str(e)}\n\n"
|
||||
f"XPU call was originally invoked at:\n\n{''.join(orig_traceback)}"
|
||||
)
|
||||
raise Exception(msg) from e # noqa: TRY002
|
||||
finally:
|
||||
delattr(_tls, "is_initializing")
|
||||
_initialized = True
|
||||
|
||||
|
||||
class _DeviceGuard:
|
||||
def __init__(self, index: int) -> None:
|
||||
self.idx = index
|
||||
self.prev_idx = -1
|
||||
|
||||
def __enter__(self):
|
||||
self.prev_idx = torch.xpu._exchange_device(self.idx)
|
||||
|
||||
def __exit__(self, type: Any, value: Any, traceback: Any):
|
||||
self.idx = torch.xpu._maybe_exchange_device(self.prev_idx)
|
||||
return False
|
||||
|
||||
|
||||
class device:
|
||||
r"""Context-manager that changes the selected device.
|
||||
|
||||
Args:
|
||||
device (torch.device or int or str): device index to select. It's a no-op if
|
||||
this argument is a negative integer or ``None``.
|
||||
"""
|
||||
|
||||
def __init__(self, device: Any) -> None:
|
||||
self.idx = _get_device_index(device, optional=True)
|
||||
self.prev_idx = -1
|
||||
|
||||
def __enter__(self):
|
||||
self.prev_idx = torch.xpu._exchange_device(self.idx)
|
||||
|
||||
def __exit__(self, type: Any, value: Any, traceback: Any):
|
||||
self.idx = torch.xpu._maybe_exchange_device(self.prev_idx)
|
||||
return False
|
||||
|
||||
|
||||
class device_of(device):
|
||||
r"""Context-manager that changes the current device to that of given object.
|
||||
|
||||
You can use both tensors and storages as arguments. If a given object is
|
||||
not allocated on a XPU, this is a no-op.
|
||||
|
||||
Args:
|
||||
obj (Tensor or Storage): object allocated on the selected device.
|
||||
"""
|
||||
|
||||
def __init__(self, obj) -> None:
|
||||
idx = obj.get_device() if obj.is_xpu else -1
|
||||
super().__init__(idx)
|
||||
|
||||
|
||||
def set_device(device: Device) -> None:
|
||||
r"""Set the current device.
|
||||
|
||||
Args:
|
||||
device (torch.device or int or str): selected device. This function is a
|
||||
no-op if this argument is negative.
|
||||
"""
|
||||
_lazy_init()
|
||||
device = _get_device_index(device)
|
||||
if device >= 0:
|
||||
torch._C._xpu_setDevice(device)
|
||||
|
||||
|
||||
def get_device_name(device: Device = None) -> str:
|
||||
r"""Get the name of a device.
|
||||
|
||||
Args:
|
||||
device (torch.device or int or str, optional): device for which to
|
||||
return the name. This function is a no-op if this argument is a
|
||||
negative integer. It uses the current device, given by :func:`~torch.xpu.current_device`,
|
||||
if :attr:`device` is ``None`` (default).
|
||||
|
||||
Returns:
|
||||
str: the name of the device
|
||||
"""
|
||||
return get_device_properties(device).name
|
||||
|
||||
|
||||
@lru_cache(None)
|
||||
def get_device_capability(device: Device = None) -> dict[str, Any]:
|
||||
r"""Get the xpu capability of a device.
|
||||
|
||||
Args:
|
||||
device (torch.device or int or str, optional): device for which to
|
||||
return the device capability. This function is a no-op if this
|
||||
argument is a negative integer. It uses the current device, given by
|
||||
:func:`~torch.xpu.current_device`, if :attr:`device` is ``None``
|
||||
(default).
|
||||
|
||||
Returns:
|
||||
dict[str, Any]: the xpu capability dictionary of the device
|
||||
"""
|
||||
props = get_device_properties(device)
|
||||
# Only keep attributes that are safe for dictionary serialization.
|
||||
serializable_types = (int, float, bool, str, type(None), list, tuple, dict)
|
||||
return {
|
||||
key: value
|
||||
for key in dir(props)
|
||||
if not key.startswith("__")
|
||||
and isinstance((value := getattr(props, key)), serializable_types)
|
||||
}
|
||||
|
||||
|
||||
def get_device_properties(
|
||||
device: Device = None,
|
||||
) -> _XpuDeviceProperties:
|
||||
r"""Get the properties of a device. Returns _XpuDeviceProperties containing the following device properties:
|
||||
|
||||
- ``name`` (str): device name.
|
||||
- ``platform_name`` (str): SYCL platform name.
|
||||
- ``vendor`` (str): device vendor.
|
||||
- ``device_id`` (int): device identifier (product ID).
|
||||
- ``driver_version`` (str): driver version.
|
||||
- ``version`` (str): runtime version.
|
||||
- ``max_compute_units`` (int): number of parallel compute units.
|
||||
- ``gpu_eu_count`` (int): number of EUs (Execution Unit).
|
||||
- ``max_work_group_size``: (int): maximum number of work-items permitted in a work-group.
|
||||
- ``max_num_sub_groups`` (int): maximum number of sub-groups supported in a work-group.
|
||||
- ``memory_clock_rate`` (int) maximum clock rate of device's global memory in MHz.
|
||||
- ``memory_bus_width`` (int) maximum bus width between device and memory in bits.
|
||||
- ``sub_group_sizes``: (list[int]): a list of supported sub-group sizes.
|
||||
- ``local_mem_size`` (int): device local memory capacity that can be allocated per work-group in bytes.
|
||||
- ``has_fp16`` (bool): whether float16 dtype is supported.
|
||||
- ``has_fp64`` (bool): whether float64 dtype is supported.
|
||||
- ``has_atomic64`` (bool): whether 64-bit atomic operations are supported.
|
||||
- ``has_bfloat16_conversions`` (bool): whether bfloat16 conversions are supported.
|
||||
- ``has_subgroup_matrix_multiply_accumulate`` (bool): whether DPAS (Dot Product Accumulate Systolic) is supported.
|
||||
- ``has_subgroup_matrix_multiply_accumulate_tensor_float32`` (bool): whether DPAS with tf32 inputs is supported.
|
||||
- ``has_subgroup_2d_block_io`` (bool): whether 2D block I/O for efficient matrix multiplication is supported.
|
||||
- ``total_memory`` (int): device global memory in bytes.
|
||||
- ``gpu_subslice_count`` (int): number of subslice.
|
||||
- ``architecture`` (int): device architecture identifier (experimental).
|
||||
- ``type`` (str): device type, e.g. 'cpu', 'gpu', accelerator', 'host', 'unknown'.
|
||||
- ``uuid`` (Any): device UUID (Universal Unique ID), 16 bytes.
|
||||
|
||||
Args:
|
||||
device (torch.device or int or str): device for which to return the
|
||||
properties of the device.
|
||||
|
||||
Returns:
|
||||
_XpuDeviceProperties: the properties of the device
|
||||
"""
|
||||
_lazy_init()
|
||||
device = _get_device_index(device, optional=True)
|
||||
return _get_device_properties(device) # type: ignore[name-defined] # noqa: F821
|
||||
|
||||
|
||||
def current_device() -> int:
|
||||
r"""Return the index of a currently selected device."""
|
||||
_lazy_init()
|
||||
return torch._C._xpu_getDevice()
|
||||
|
||||
|
||||
def _get_device(device: int | str | torch.device) -> torch.device:
|
||||
r"""Return the torch.device type object from the passed in device.
|
||||
|
||||
Args:
|
||||
device (torch.device or int or str): selected device.
|
||||
"""
|
||||
if isinstance(device, str):
|
||||
device = torch.device(device)
|
||||
elif isinstance(device, int):
|
||||
device = torch.device("xpu", device)
|
||||
return device
|
||||
|
||||
|
||||
def can_device_access_peer(device: Device, peer: Device) -> bool:
|
||||
r"""Query whether a device can access a peer device's memory.
|
||||
|
||||
Args:
|
||||
device (torch.device or int or str): selected device.
|
||||
peer (torch.device or int or str): peer device to query access to.
|
||||
|
||||
Returns:
|
||||
bool: ``True`` if ``device`` can access ``peer``, ``False`` otherwise.
|
||||
"""
|
||||
_lazy_init()
|
||||
device = _get_device_index(device, optional=True)
|
||||
peer = _get_device_index(peer, optional=True)
|
||||
return torch._C._xpu_canDeviceAccessPeer(device, peer)
|
||||
|
||||
|
||||
class StreamContext:
|
||||
r"""Context-manager that selects a given stream.
|
||||
|
||||
All XPU kernels queued within its context will be enqueued on a selected
|
||||
stream.
|
||||
|
||||
Args:
|
||||
Stream (Stream): selected stream. This manager is a no-op if it's
|
||||
``None``.
|
||||
.. note:: Streams are per-device.
|
||||
"""
|
||||
|
||||
cur_stream: torch.xpu.Stream | None
|
||||
|
||||
def __init__(self, stream: torch.xpu.Stream | None) -> None:
|
||||
self.stream = stream
|
||||
self.idx = _get_device_index(None, True)
|
||||
if self.idx is None:
|
||||
self.idx = -1 # pyrefly: ignore [bad-assignment]
|
||||
|
||||
def __enter__(self):
|
||||
cur_stream = self.stream
|
||||
if cur_stream is None or self.idx == -1:
|
||||
return
|
||||
self.src_prev_stream = torch.xpu.current_stream(None)
|
||||
|
||||
# If the stream is not on the current device, then set the current stream on the device
|
||||
if self.src_prev_stream.device != cur_stream.device:
|
||||
with device(cur_stream.device):
|
||||
self.dst_prev_stream = torch.xpu.current_stream(cur_stream.device)
|
||||
torch.xpu.set_stream(cur_stream)
|
||||
|
||||
def __exit__(self, type: Any, value: Any, traceback: Any):
|
||||
cur_stream = self.stream
|
||||
if cur_stream is None or self.idx == -1:
|
||||
return
|
||||
|
||||
# Reset the stream on the original device and destination device
|
||||
if self.src_prev_stream.device != cur_stream.device:
|
||||
torch.xpu.set_stream(self.dst_prev_stream)
|
||||
torch.xpu.set_stream(self.src_prev_stream)
|
||||
|
||||
|
||||
def stream(stream: torch.xpu.Stream | None) -> StreamContext:
|
||||
r"""Wrap around the Context-manager StreamContext that selects a given stream.
|
||||
|
||||
Arguments:
|
||||
stream (Stream): selected stream. This manager is a no-op if it's ``None``.
|
||||
"""
|
||||
return StreamContext(stream)
|
||||
|
||||
|
||||
def _set_stream_by_id(stream_id, device_index, device_type) -> None:
|
||||
r"""set stream specified by the stream id, device index and device type
|
||||
|
||||
Args: stream_id (int): not visible to the user, used to assigned to the specific stream.
|
||||
device_index (int): selected device index.
|
||||
device_type (int): selected device type.
|
||||
"""
|
||||
torch._C._xpu_setStream(
|
||||
stream_id=stream_id,
|
||||
device_index=device_index,
|
||||
device_type=device_type,
|
||||
)
|
||||
|
||||
|
||||
def set_stream(stream: Stream) -> None:
|
||||
r"""Set the current stream. This is a wrapper API to set the stream.
|
||||
Usage of this function is discouraged in favor of the ``stream``
|
||||
context manager.
|
||||
|
||||
Args:
|
||||
stream (Stream): selected stream. This function is a no-op
|
||||
if this argument is ``None``.
|
||||
"""
|
||||
if stream is None:
|
||||
return
|
||||
_lazy_init()
|
||||
_set_stream_by_id(
|
||||
stream_id=stream.stream_id,
|
||||
device_index=stream.device_index,
|
||||
device_type=stream.device_type,
|
||||
)
|
||||
|
||||
|
||||
def current_stream(device: Device = None) -> Stream:
|
||||
r"""Return the currently selected :class:`Stream` for a given device.
|
||||
|
||||
Args:
|
||||
device (torch.device or int, optional): selected device. Returns
|
||||
the currently selected :class:`Stream` for the current device, given
|
||||
by :func:`~torch.xpu.current_device`, if :attr:`device` is ``None``
|
||||
(default).
|
||||
"""
|
||||
_lazy_init()
|
||||
streamdata = torch._C._xpu_getCurrentStream(
|
||||
_get_device_index(device, optional=True)
|
||||
)
|
||||
return Stream(
|
||||
stream_id=streamdata[0], device_index=streamdata[1], device_type=streamdata[2]
|
||||
)
|
||||
|
||||
|
||||
def get_stream_from_external(data_ptr: int, device: Device = None) -> Stream:
|
||||
r"""Return a :class:`Stream` from an external SYCL queue.
|
||||
|
||||
This function is used to wrap SYCL queue created in other libraries in order
|
||||
to facilitate data exchange and multi-library interactions.
|
||||
|
||||
.. note:: This function doesn't manage the queue life-cycle, it is the user
|
||||
responsibility to keep the referenced queue alive while this returned stream is
|
||||
being used. The different SYCL queue pointers will result in distinct
|
||||
:class:`Stream` objects, even if the SYCL queues they dereference are equivalent.
|
||||
|
||||
Args:
|
||||
data_ptr(int): Integer representation of the `sycl::queue*` value passed externally.
|
||||
device(torch.device or int, optional): the device where the queue was originally created.
|
||||
It is the user responsibility to ensure the device is specified correctly.
|
||||
"""
|
||||
_lazy_init()
|
||||
streamdata = torch._C._xpu_getStreamFromExternal(
|
||||
data_ptr, _get_device_index(device, optional=True)
|
||||
)
|
||||
return Stream(
|
||||
stream_id=streamdata[0], device_index=streamdata[1], device_type=streamdata[2]
|
||||
)
|
||||
|
||||
|
||||
def synchronize(device: Device = None) -> None:
|
||||
r"""Wait for all kernels in all streams on a XPU device to complete.
|
||||
|
||||
Args:
|
||||
device (torch.device or int, optional): device for which to synchronize.
|
||||
It uses the current device, given by :func:`~torch.xpu.current_device`,
|
||||
if :attr:`device` is ``None`` (default).
|
||||
"""
|
||||
_lazy_init()
|
||||
device = _get_device_index(device, optional=True)
|
||||
return torch._C._xpu_synchronize(device)
|
||||
|
||||
|
||||
def get_arch_list() -> list[str]:
|
||||
r"""Return list XPU architectures this library was compiled for."""
|
||||
if not _is_compiled():
|
||||
return []
|
||||
arch_flags = torch._C._xpu_getArchFlags()
|
||||
if arch_flags is None:
|
||||
return []
|
||||
return arch_flags.split()
|
||||
|
||||
|
||||
def get_gencode_flags() -> str:
|
||||
r"""Return XPU AOT(ahead-of-time) build flags this library was compiled with."""
|
||||
arch_list = get_arch_list()
|
||||
if len(arch_list) == 0:
|
||||
return ""
|
||||
return f"-device {','.join(arch for arch in arch_list)}"
|
||||
|
||||
|
||||
def _get_generator(device: torch.device) -> torch._C.Generator:
|
||||
r"""Return the XPU Generator object for the given device.
|
||||
|
||||
Args:
|
||||
device (torch.device): selected device.
|
||||
"""
|
||||
idx = device.index
|
||||
if idx is None:
|
||||
idx = current_device()
|
||||
return torch.xpu.default_generators[idx]
|
||||
|
||||
|
||||
def _set_rng_state_offset(
|
||||
offset: int, device: int | str | torch.device = "xpu"
|
||||
) -> None:
|
||||
r"""Set the random number generator state offset of the specified GPU.
|
||||
|
||||
Args:
|
||||
offset (int): The desired offset
|
||||
device (torch.device or int, optional): The device to set the RNG state.
|
||||
Default: ``'xpu'`` (i.e., ``torch.device('xpu')``, the current XPU device).
|
||||
"""
|
||||
final_device = _get_device(device)
|
||||
|
||||
def cb() -> None:
|
||||
default_generator = _get_generator(final_device)
|
||||
default_generator.set_offset(offset)
|
||||
|
||||
_lazy_call(cb)
|
||||
|
||||
|
||||
def _get_rng_state_offset(device: int | str | torch.device = "xpu") -> int:
|
||||
r"""Return the random number generator state offset of the specified GPU.
|
||||
|
||||
Args:
|
||||
device (torch.device or int, optional): The device to return the RNG state offset of.
|
||||
Default: ``'xpu'`` (i.e., ``torch.device('xpu')``, the current XPU device).
|
||||
|
||||
.. warning::
|
||||
This function eagerly initializes XPU.
|
||||
"""
|
||||
_lazy_init()
|
||||
final_device = _get_device(device)
|
||||
default_generator = _get_generator(final_device)
|
||||
return default_generator.get_offset()
|
||||
|
||||
|
||||
# import here to avoid circular import
|
||||
from .memory import (
|
||||
change_current_allocator,
|
||||
empty_cache,
|
||||
get_per_process_memory_fraction,
|
||||
max_memory_allocated,
|
||||
max_memory_reserved,
|
||||
mem_get_info,
|
||||
memory_allocated,
|
||||
memory_reserved,
|
||||
memory_snapshot,
|
||||
memory_stats,
|
||||
memory_stats_as_nested_dict,
|
||||
MemPool,
|
||||
reset_accumulated_memory_stats,
|
||||
reset_peak_memory_stats,
|
||||
set_per_process_memory_fraction,
|
||||
use_mem_pool,
|
||||
XPUPluggableAllocator,
|
||||
)
|
||||
from .random import (
|
||||
get_rng_state,
|
||||
get_rng_state_all,
|
||||
initial_seed,
|
||||
manual_seed,
|
||||
manual_seed_all,
|
||||
seed,
|
||||
seed_all,
|
||||
set_rng_state,
|
||||
set_rng_state_all,
|
||||
)
|
||||
|
||||
|
||||
_POOL_HANDLE = NewType("_POOL_HANDLE", tuple[int, int])
|
||||
__all__ = [
|
||||
"Event",
|
||||
"Stream",
|
||||
"StreamContext",
|
||||
"XPUPluggableAllocator",
|
||||
"XPUGraph",
|
||||
"can_device_access_peer",
|
||||
"change_current_allocator",
|
||||
"current_device",
|
||||
"current_stream",
|
||||
"default_generators",
|
||||
"device",
|
||||
"device_of",
|
||||
"device_count",
|
||||
"empty_cache",
|
||||
"get_arch_list",
|
||||
"get_device_capability",
|
||||
"get_device_name",
|
||||
"get_device_properties",
|
||||
"get_gencode_flags",
|
||||
"get_per_process_memory_fraction",
|
||||
"get_rng_state",
|
||||
"get_rng_state_all",
|
||||
"get_stream_from_external",
|
||||
"graph",
|
||||
"graph_pool_handle",
|
||||
"init",
|
||||
"initial_seed",
|
||||
"is_available",
|
||||
"is_bf16_supported",
|
||||
"is_current_stream_capturing",
|
||||
"is_initialized",
|
||||
"is_tf32_supported",
|
||||
"make_graphed_callables",
|
||||
"manual_seed",
|
||||
"manual_seed_all",
|
||||
"max_memory_allocated",
|
||||
"max_memory_reserved",
|
||||
"mem_get_info",
|
||||
"memory_allocated",
|
||||
"memory_reserved",
|
||||
"memory_snapshot",
|
||||
"memory_stats",
|
||||
"memory_stats_as_nested_dict",
|
||||
"MemPool",
|
||||
"use_mem_pool",
|
||||
"reset_accumulated_memory_stats",
|
||||
"reset_peak_memory_stats",
|
||||
"seed",
|
||||
"seed_all",
|
||||
"set_device",
|
||||
"set_per_process_memory_fraction",
|
||||
"set_rng_state",
|
||||
"set_rng_state_all",
|
||||
"set_stream",
|
||||
"stream",
|
||||
"streams",
|
||||
"synchronize",
|
||||
]
|
||||
@@ -0,0 +1,69 @@
|
||||
from collections.abc import Callable
|
||||
|
||||
from torch._utils import CallbackRegistry
|
||||
|
||||
|
||||
EventCreationCallbacks: "CallbackRegistry[int]" = CallbackRegistry("XPU event creation")
|
||||
EventDeletionCallbacks: "CallbackRegistry[int]" = CallbackRegistry("XPU event deletion")
|
||||
EventRecordCallbacks: "CallbackRegistry[int, int]" = CallbackRegistry(
|
||||
"XPU event record"
|
||||
)
|
||||
EventWaitCallbacks: "CallbackRegistry[int, int]" = CallbackRegistry("XPU event wait")
|
||||
MemoryAllocationCallbacks: "CallbackRegistry[int]" = CallbackRegistry(
|
||||
"XPU memory allocation"
|
||||
)
|
||||
MemoryDeallocationCallbacks: "CallbackRegistry[int]" = CallbackRegistry(
|
||||
"XPU memory deallocation"
|
||||
)
|
||||
StreamCreationCallbacks: "CallbackRegistry[int]" = CallbackRegistry(
|
||||
"XPU stream creation"
|
||||
)
|
||||
DeviceSynchronizationCallbacks: "CallbackRegistry[[]]" = CallbackRegistry(
|
||||
"XPU device synchronization"
|
||||
)
|
||||
StreamSynchronizationCallbacks: "CallbackRegistry[int]" = CallbackRegistry(
|
||||
"XPU stream synchronization"
|
||||
)
|
||||
EventSynchronizationCallbacks: "CallbackRegistry[int]" = CallbackRegistry(
|
||||
"XPU event synchronization"
|
||||
)
|
||||
|
||||
|
||||
def register_callback_for_event_creation(cb: Callable[[int], None]) -> None:
|
||||
EventCreationCallbacks.add_callback(cb)
|
||||
|
||||
|
||||
def register_callback_for_event_deletion(cb: Callable[[int], None]) -> None:
|
||||
EventDeletionCallbacks.add_callback(cb)
|
||||
|
||||
|
||||
def register_callback_for_event_record(cb: Callable[[int, int], None]) -> None:
|
||||
EventRecordCallbacks.add_callback(cb)
|
||||
|
||||
|
||||
def register_callback_for_event_wait(cb: Callable[[int, int], None]) -> None:
|
||||
EventWaitCallbacks.add_callback(cb)
|
||||
|
||||
|
||||
def register_callback_for_memory_allocation(cb: Callable[[int], None]) -> None:
|
||||
MemoryAllocationCallbacks.add_callback(cb)
|
||||
|
||||
|
||||
def register_callback_for_memory_deallocation(cb: Callable[[int], None]) -> None:
|
||||
MemoryDeallocationCallbacks.add_callback(cb)
|
||||
|
||||
|
||||
def register_callback_for_stream_creation(cb: Callable[[int], None]) -> None:
|
||||
StreamCreationCallbacks.add_callback(cb)
|
||||
|
||||
|
||||
def register_callback_for_device_synchronization(cb: Callable[[], None]) -> None:
|
||||
DeviceSynchronizationCallbacks.add_callback(cb)
|
||||
|
||||
|
||||
def register_callback_for_stream_synchronization(cb: Callable[[int], None]) -> None:
|
||||
StreamSynchronizationCallbacks.add_callback(cb)
|
||||
|
||||
|
||||
def register_callback_for_event_synchronization(cb: Callable[[int], None]) -> None:
|
||||
EventSynchronizationCallbacks.add_callback(cb)
|
||||
@@ -0,0 +1,39 @@
|
||||
from typing import Any
|
||||
|
||||
import torch
|
||||
|
||||
# The _get_device_index has been moved to torch.utils._get_device_index
|
||||
from torch._utils import _get_device_index as _torch_get_device_index
|
||||
|
||||
|
||||
def _get_device_index(
|
||||
device: Any, optional: bool = False, allow_cpu: bool = False
|
||||
) -> int:
|
||||
r"""Get the device index from :attr:`device`, which can be a torch.device
|
||||
object, a Python integer, or ``None``.
|
||||
|
||||
If :attr:`device` is a torch.device object, returns the device index if it
|
||||
is a XPU device. Note that for a XPU device without a specified index,
|
||||
i.e., ``torch.device('xpu')``, this will return the current default XPU
|
||||
device if :attr:`optional` is ``True``. If :attr:`allow_cpu` is ``True``,
|
||||
CPU devices will be accepted and ``-1`` will be returned in this case.
|
||||
|
||||
If :attr:`device` is a Python integer, it is returned as is.
|
||||
|
||||
If :attr:`device` is ``None``, this will return the current default XPU
|
||||
device if :attr:`optional` is ``True``.
|
||||
"""
|
||||
if isinstance(device, int):
|
||||
return device
|
||||
if isinstance(device, str):
|
||||
device = torch.device(device)
|
||||
if isinstance(device, torch.device):
|
||||
if allow_cpu:
|
||||
if device.type not in ["xpu", "cpu"]:
|
||||
raise ValueError(f"Expected a xpu or cpu device, but got: {device}")
|
||||
elif device.type != "xpu":
|
||||
raise ValueError(f"Expected a xpu device, but got: {device}")
|
||||
if not torch.jit.is_scripting():
|
||||
if isinstance(device, torch.xpu.device):
|
||||
return device.idx
|
||||
return _torch_get_device_index(device, optional, allow_cpu)
|
||||
@@ -0,0 +1,529 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
from collections.abc import Callable
|
||||
from typing import overload, TYPE_CHECKING, TypeAlias
|
||||
from typing_extensions import ParamSpec, Self, TypeVar
|
||||
|
||||
import torch
|
||||
from torch import Tensor
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from torch.xpu import _POOL_HANDLE
|
||||
|
||||
from .._utils import _dummy_type
|
||||
|
||||
|
||||
__all__ = [
|
||||
"is_current_stream_capturing",
|
||||
"graph_pool_handle",
|
||||
"XPUGraph",
|
||||
"graph",
|
||||
"make_graphed_callables",
|
||||
]
|
||||
|
||||
_R = TypeVar("_R")
|
||||
_P = ParamSpec("_P")
|
||||
|
||||
if not hasattr(torch._C, "_XpuStreamBase"):
|
||||
# Define dummy base classes
|
||||
torch._C.__dict__["_XPUGraph"] = _dummy_type("_XPUGraph")
|
||||
torch._C.__dict__["_xpu_graph_pool_handle"] = _dummy_type("_xpu_graph_pool_handle")
|
||||
torch._C.__dict__["_xpu_isCurrentStreamCapturing"] = _dummy_type(
|
||||
"_xpu_isCurrentStreamCapturing"
|
||||
)
|
||||
|
||||
from torch._C import _xpu_graph_pool_handle, _xpu_isCurrentStreamCapturing, _XPUGraph
|
||||
|
||||
|
||||
def is_current_stream_capturing() -> bool:
|
||||
r"""Return True if XPU graph capture is underway on the current XPU stream, False otherwise.
|
||||
|
||||
If a XPU context does not exist on the current device, returns False without initializing the context.
|
||||
"""
|
||||
return _xpu_isCurrentStreamCapturing()
|
||||
|
||||
|
||||
def graph_pool_handle() -> _POOL_HANDLE:
|
||||
r"""Return an opaque token representing the id of a graph memory pool."""
|
||||
return torch.xpu._POOL_HANDLE(_xpu_graph_pool_handle())
|
||||
|
||||
|
||||
class XPUGraph(_XPUGraph):
|
||||
r"""Wrapper around a XPU graph.
|
||||
|
||||
Arguments:
|
||||
keep_graph (bool, optional): If ``keep_graph=False``, the
|
||||
executable command graph will be instantiated on GPU at the end of
|
||||
``capture_end`` and the underlying modifiable command graph will be
|
||||
destroyed. Note that the executable command graph will not be
|
||||
instantiated at the end of ``capture_end`` in this
|
||||
case. Instead, it will be instantiated via an explicit called
|
||||
to ``instantiate`` or automatically on the first call to
|
||||
``replay`` if ``instantiate`` was not already called. Calling
|
||||
``instantiate`` manually before ``replay`` is recommended to
|
||||
prevent increased latency on the first call to ``replay``.
|
||||
|
||||
"""
|
||||
|
||||
def __new__(cls, keep_graph: bool = False) -> Self:
|
||||
return super().__new__(cls, keep_graph)
|
||||
|
||||
def capture_begin(self, pool: _POOL_HANDLE | None = None) -> None:
|
||||
r"""Begin capturing XPU work on the current xpu stream.
|
||||
|
||||
Typically, you shouldn't call ``capture_begin`` yourself.
|
||||
Use :class:`~torch.xpu.graph`, which call ``capture_begin`` internally.
|
||||
|
||||
Arguments:
|
||||
pool (optional): Token (returned by :func:`~torch.xpu.graph_pool_handle` or
|
||||
:meth:`other_Graph_instance.pool()<torch.xpu.XPUGraph.pool>`) that hints this graph may share memory
|
||||
with the indicated pool.
|
||||
"""
|
||||
super().capture_begin(pool=pool)
|
||||
|
||||
def capture_end(self) -> None:
|
||||
r"""End XPU graph capture on the current stream.
|
||||
|
||||
After ``capture_end``, ``replay`` may be called on this instance.
|
||||
|
||||
Typically, you shouldn't call ``capture_end`` yourself.
|
||||
Use :class:`~torch.xpu.graph`, which call ``capture_end`` internally.
|
||||
"""
|
||||
super().capture_end()
|
||||
|
||||
def instantiate(self) -> None:
|
||||
r"""Instantiate the XPU 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. Does not destroy the xpu modify command graph returned
|
||||
by ``raw_xpu_graph``.
|
||||
"""
|
||||
super().instantiate()
|
||||
|
||||
def replay(self) -> None:
|
||||
r"""Replay the XPU work captured by this graph."""
|
||||
super().replay()
|
||||
|
||||
def reset(self) -> None:
|
||||
r"""Delete the graph currently held by this instance."""
|
||||
super().reset()
|
||||
|
||||
def pool(self) -> _POOL_HANDLE:
|
||||
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.
|
||||
"""
|
||||
return super().pool()
|
||||
|
||||
def enable_debug_mode(self) -> None:
|
||||
r"""Enable debugging mode for XPUGraph.debug_dump."""
|
||||
return super().enable_debug_mode()
|
||||
|
||||
def debug_dump(self, debug_path: str) -> None:
|
||||
r"""
|
||||
Arguments:
|
||||
debug_path (required): Path to dump the graph to.
|
||||
|
||||
Calls a debugging function to dump the graph if the debugging is
|
||||
enabled via XPUGraph.enable_debug_mode()
|
||||
"""
|
||||
return super().debug_dump(debug_path)
|
||||
|
||||
def raw_xpu_graph(self) -> int:
|
||||
r"""Returns the underlying xpuGraph_t. ``keep_graph`` must be True.
|
||||
|
||||
XPU doesn't provide APIs to manipulate this object.
|
||||
""" # noqa: B950
|
||||
return super().raw_xpu_graph()
|
||||
|
||||
def raw_xpu_graph_exec(self) -> int:
|
||||
r"""Returns the underlying xpuGraphExec_t. ``instantiate`` must have been called if ``keep_graph`` is True, or ``capture_end`` must have been called if ``keep_graph`` is False. If you call ``instantiate()`` after ``raw_xpu_graph_exec()``, the previously returned xpuGraphExec_t will be destroyed. It is your responsibility not to use this object after destruction.
|
||||
|
||||
XPU doesn't provide APIs to manipulate this object.
|
||||
""" # noqa: B950
|
||||
return super().raw_xpu_graph_exec()
|
||||
|
||||
|
||||
class graph:
|
||||
r"""Context-manager that captures XPU work into a :class:`torch.xpu.XPUGraph` object for later replay.
|
||||
|
||||
Arguments:
|
||||
xpu_graph (torch.xpu.XPUGraph): Graph object used for capture.
|
||||
pool (optional): Opaque token (returned by a call to :func:`~torch.xpu.graph_pool_handle()` or
|
||||
:meth:`other_Graph_instance.pool()<torch.xpu.XPUGraph.pool>`) hinting this graph's capture
|
||||
may share memory from the specified pool.
|
||||
stream (torch.xpu.Stream, optional): If supplied, will be set as the current stream in the context.
|
||||
If not supplied, ``graph`` sets its own internal side stream as the current stream in the context.
|
||||
|
||||
.. note::
|
||||
For effective memory sharing, if you pass a ``pool`` used by a previous capture and the previous capture
|
||||
used an explicit ``stream`` argument, you should pass the same ``stream`` argument to this capture.
|
||||
|
||||
""" # noqa: B950
|
||||
|
||||
default_capture_stream: torch.xpu.Stream | None = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
xpu_graph: XPUGraph,
|
||||
pool: _POOL_HANDLE | None = None,
|
||||
stream: torch.xpu.Stream | None = None,
|
||||
):
|
||||
# Lazy-init of default_capture_stream helps avoid circular-import errors.
|
||||
# Not thread safe, but graphs already have the general (explicitly documented)
|
||||
# restriction that only one capture may be underway at a time in the process.
|
||||
if self.__class__.default_capture_stream is None:
|
||||
self.__class__.default_capture_stream = torch.xpu.Stream()
|
||||
|
||||
self.pool: tuple[()] | tuple[_POOL_HANDLE] = () if pool is None else (pool,)
|
||||
self.capture_stream = (
|
||||
stream if stream is not None else self.__class__.default_capture_stream
|
||||
)
|
||||
if self.capture_stream is None:
|
||||
raise AssertionError("capture_stream must not be None")
|
||||
self.stream_ctx = self.capture_stream
|
||||
self.xpu_graph = xpu_graph
|
||||
|
||||
def __enter__(self) -> None:
|
||||
# Free as much memory as we can for the graph
|
||||
torch.xpu.synchronize()
|
||||
|
||||
torch.xpu.empty_cache()
|
||||
self.stream_ctx.__enter__()
|
||||
|
||||
self.xpu_graph.capture_begin(*self.pool)
|
||||
|
||||
def __exit__(self, *args: object) -> None:
|
||||
self.xpu_graph.capture_end()
|
||||
self.stream_ctx.__exit__(*args)
|
||||
|
||||
|
||||
_ModuleOrCallable: TypeAlias = torch.nn.Module | Callable[..., object]
|
||||
|
||||
|
||||
@overload
|
||||
def make_graphed_callables(
|
||||
callables: _ModuleOrCallable,
|
||||
sample_args: tuple[Tensor, ...],
|
||||
num_warmup_iters: int = 3,
|
||||
allow_unused_input: bool = False,
|
||||
pool: _POOL_HANDLE | None = None,
|
||||
) -> _ModuleOrCallable: ...
|
||||
|
||||
|
||||
@overload
|
||||
def make_graphed_callables(
|
||||
callables: tuple[_ModuleOrCallable, ...],
|
||||
sample_args: tuple[tuple[Tensor, ...], ...],
|
||||
num_warmup_iters: int = 3,
|
||||
allow_unused_input: bool = False,
|
||||
pool: _POOL_HANDLE | None = None,
|
||||
) -> tuple[_ModuleOrCallable, ...]: ...
|
||||
|
||||
|
||||
def make_graphed_callables(
|
||||
callables: _ModuleOrCallable | tuple[_ModuleOrCallable, ...],
|
||||
sample_args: tuple[Tensor, ...] | tuple[tuple[Tensor, ...], ...],
|
||||
num_warmup_iters: int = 3,
|
||||
allow_unused_input: bool = False,
|
||||
pool: _POOL_HANDLE | None = None,
|
||||
) -> _ModuleOrCallable | tuple[_ModuleOrCallable, ...]:
|
||||
r"""Accept callables (functions or :class:`nn.Module<torch.nn.Module>`\ s) and returns graphed versions.
|
||||
|
||||
Each graphed callable's forward pass runs its source callable's
|
||||
forward XPU work as a XPU graph inside a single autograd node.
|
||||
|
||||
The graphed callable's forward pass also appends
|
||||
a backward node to the autograd graph. During backward, this node runs the
|
||||
callable's backward work as a XPU graph.
|
||||
|
||||
Therefore, each graphed callable should be a drop-in replacement for its source callable
|
||||
in an autograd-enabled training loop.
|
||||
|
||||
See :ref:`Partial-network capture<partial-network-capture>` for detailed use and constraints.
|
||||
|
||||
If you pass a tuple of several callables, their captures will use the same memory pool.
|
||||
|
||||
Arguments:
|
||||
callables (torch.nn.Module or Python function, or tuple of these): Callable or callables to graph.
|
||||
If you pass a tuple of callables, their order in the tuple must be the same order they'll run
|
||||
in the live workload.
|
||||
sample_args (tuple of Tensors, or tuple of tuples of Tensors): Samples args for each callable.
|
||||
If a single callable was passed, ``sample_args`` must be a single tuple of argument Tensors.
|
||||
If a tuple of callables was passed, ``sample_args`` must be tuple of tuples of argument Tensors.
|
||||
num_warmup_iters (int): The number of warmup iterations. Currently, ``DataDistributedParallel`` needs
|
||||
11 iterations for warm up. Default: ``3``.
|
||||
allow_unused_input (bool): If False, specifying inputs that were not used when computing outputs
|
||||
(and therefore their grad is always zero) is an error. Defaults to False.
|
||||
pool (optional): Token (returned by :func:`~torch.xpu.graph_pool_handle` or
|
||||
:meth:`other_Graph_instance.pool()<torch.xpu.XPUGraph.pool>`) that hints this graph may share memory
|
||||
with the indicated pool.
|
||||
.. note::
|
||||
The ``requires_grad`` state of each Tensor in ``sample_args`` must match the state
|
||||
that's expected for the corresponding real input in the training loop.
|
||||
|
||||
.. warning::
|
||||
This API is in beta and may change in future releases.
|
||||
|
||||
.. warning::
|
||||
``sample_args`` for each callable must contain only Tensors. Other types are not allowed.
|
||||
|
||||
.. warning::
|
||||
Returned callables do not support higher order differentiation (e.g., double backward).
|
||||
|
||||
.. warning::
|
||||
In any :class:`~torch.nn.Module` passed to :func:`~make_graphed_callables`, only parameters
|
||||
may be trainable. Buffers must have ``requires_grad=False``.
|
||||
|
||||
.. warning::
|
||||
After you pass a :class:`torch.nn.Module` through :func:`~make_graphed_callables`,
|
||||
you may not add or remove any of that Module's parameters or buffers.
|
||||
|
||||
.. warning::
|
||||
:class:`torch.nn.Module`\s passed to :func:`~torch.xpu.make_graphed_callables` must not have module hooks
|
||||
registered on them at the time they are passed. However, registering hooks on modules *after* passing them
|
||||
through :func:`~torch.xpu.make_graphed_callables` is allowed.
|
||||
|
||||
.. warning::
|
||||
When running a graphed callable, you must pass its arguments in the same order and format
|
||||
they appeared in that callable's ``sample_args``.
|
||||
|
||||
.. warning::
|
||||
The automatic mixed precision is supported in :func:`~torch.xpu.make_graphed_callables` only with disabled
|
||||
caching. The context manager `torch.amp.autocast()` must have `cache_enabled=False`.
|
||||
"""
|
||||
if torch.is_autocast_enabled() and torch.is_autocast_cache_enabled():
|
||||
raise RuntimeError(
|
||||
"make_graphed_callables does not support the autocast caching. Please set `cache_enabled=False`."
|
||||
)
|
||||
|
||||
just_one_callable = False
|
||||
|
||||
_sample_args: tuple[tuple[Tensor, ...], ...]
|
||||
if not isinstance(callables, tuple):
|
||||
just_one_callable = True
|
||||
callables = (callables,)
|
||||
_sample_args = (typing.cast(tuple[Tensor, ...], sample_args),)
|
||||
else:
|
||||
_sample_args = typing.cast(tuple[tuple[Tensor, ...], ...], sample_args)
|
||||
|
||||
flatten_sample_args = []
|
||||
|
||||
for c, args in zip(callables, _sample_args):
|
||||
if isinstance(c, torch.nn.Module):
|
||||
if not (
|
||||
len(c._backward_hooks) == 0
|
||||
and len(c._forward_hooks) == 0
|
||||
and len(c._forward_pre_hooks) == 0
|
||||
):
|
||||
raise RuntimeError(
|
||||
"Modules must not have hooks registered at the time they are passed. However, registering hooks "
|
||||
+ "on modules after passing them through make_graphed_callables is allowed."
|
||||
)
|
||||
if not all(b.requires_grad is False for b in c.buffers()):
|
||||
raise RuntimeError(
|
||||
"In any :class:`~torch.nn.Module` passed to "
|
||||
+ ":func:`~make_graphed_callables`, only parameters may be trainable. All buffers must have "
|
||||
+ "``requires_grad=False``."
|
||||
)
|
||||
flatten_arg = torch.utils._pytree.arg_tree_leaves(*args)
|
||||
flatten_sample_args.append(tuple(flatten_arg))
|
||||
if not all(isinstance(arg, torch.Tensor) for arg in flatten_arg):
|
||||
raise TypeError(
|
||||
"In the beta API, sample_args "
|
||||
+ "for each callable must contain only Tensors. Other types are not allowed."
|
||||
)
|
||||
|
||||
# If a callable is an nn.Module, its graph's full input surface is the args the user explicitly
|
||||
# passes to forward (ie, its sample_args) AND the module's parameter attributes.
|
||||
per_callable_len_user_args = [len(args) for args in flatten_sample_args]
|
||||
per_callable_module_params = [
|
||||
tuple(c.parameters()) if isinstance(c, torch.nn.Module) else ()
|
||||
for c in callables
|
||||
]
|
||||
per_callable_static_input_surfaces = [
|
||||
flatten_sample_args[i] + per_callable_module_params[i]
|
||||
for i in range(len(callables))
|
||||
]
|
||||
|
||||
fwd_graphs = [torch.xpu.XPUGraph() for _ in range(len(callables))]
|
||||
bwd_graphs = [torch.xpu.XPUGraph() for _ in range(len(callables))]
|
||||
|
||||
mempool = graph_pool_handle() if pool is None else pool
|
||||
|
||||
# Warmup
|
||||
torch.xpu.synchronize()
|
||||
with torch.xpu.stream(torch.xpu.Stream()):
|
||||
for func, args, static_input_surface in zip(
|
||||
callables, _sample_args, per_callable_static_input_surfaces
|
||||
):
|
||||
grad_inputs, outputs, outputs_grad = None, None, None
|
||||
for _ in range(num_warmup_iters):
|
||||
outputs = torch.utils._pytree.tree_leaves(func(*args))
|
||||
outputs_grad = tuple(o for o in outputs if o.requires_grad)
|
||||
if len(outputs_grad) > 0:
|
||||
grad_inputs = torch.autograd.grad(
|
||||
outputs=outputs_grad,
|
||||
inputs=tuple(
|
||||
i for i in static_input_surface if i.requires_grad
|
||||
),
|
||||
grad_outputs=tuple(
|
||||
torch.empty_like(o) for o in outputs if o.requires_grad
|
||||
),
|
||||
only_inputs=True,
|
||||
allow_unused=allow_unused_input,
|
||||
)
|
||||
for v in [outputs, outputs_grad, grad_inputs]:
|
||||
del v
|
||||
|
||||
torch.xpu.synchronize()
|
||||
|
||||
# Capture forward graphs
|
||||
per_callable_static_outputs = []
|
||||
per_callable_output_unflatten_spec = []
|
||||
for func, args, fwd_graph in zip(callables, _sample_args, fwd_graphs):
|
||||
# each graph uses the same mempool
|
||||
with torch.xpu.graph(fwd_graph, pool=mempool):
|
||||
func_outputs = func(*args)
|
||||
|
||||
flatten_outputs, spec = torch.utils._pytree.tree_flatten(func_outputs)
|
||||
per_callable_static_outputs.append(tuple(flatten_outputs))
|
||||
per_callable_output_unflatten_spec.append(spec)
|
||||
|
||||
# Capture backward graphs in reverse order
|
||||
per_callable_static_grad_outputs = []
|
||||
per_callable_static_grad_inputs = []
|
||||
for static_input_surface, static_outputs, bwd_graph in zip(
|
||||
reversed(per_callable_static_input_surfaces),
|
||||
reversed(per_callable_static_outputs),
|
||||
reversed(bwd_graphs),
|
||||
):
|
||||
static_grad_outputs = tuple(
|
||||
torch.empty_like(o) if o.requires_grad else None for o in static_outputs
|
||||
)
|
||||
|
||||
outputs_grad = tuple(o for o in static_outputs if o.requires_grad)
|
||||
grad_inputs = None
|
||||
if len(outputs_grad) > 0:
|
||||
with torch.xpu.graph(bwd_graph, pool=mempool):
|
||||
grad_inputs = torch.autograd.grad(
|
||||
outputs=outputs_grad,
|
||||
inputs=tuple(i for i in static_input_surface if i.requires_grad),
|
||||
grad_outputs=tuple(o for o in static_grad_outputs if o is not None),
|
||||
only_inputs=True,
|
||||
allow_unused=allow_unused_input,
|
||||
)
|
||||
|
||||
static_grad_inputs = []
|
||||
grad_idx = 0
|
||||
for arg in static_input_surface:
|
||||
if arg.requires_grad and grad_inputs is not None:
|
||||
static_grad_inputs.append(grad_inputs[grad_idx])
|
||||
grad_idx += 1
|
||||
else:
|
||||
static_grad_inputs.append(None) # type: ignore[arg-type]
|
||||
static_grad_inputs = tuple(static_grad_inputs) # type: ignore[assignment]
|
||||
|
||||
per_callable_static_grad_outputs.append(static_grad_outputs)
|
||||
per_callable_static_grad_inputs.append(static_grad_inputs)
|
||||
|
||||
# Reverses the most recent two lists
|
||||
per_callable_static_grad_outputs.reverse()
|
||||
per_callable_static_grad_inputs.reverse()
|
||||
|
||||
def make_graphed_autograd_function(
|
||||
fwd_graph: XPUGraph,
|
||||
bwd_graph: XPUGraph,
|
||||
module_params: tuple[torch.nn.Parameter, ...],
|
||||
len_user_args: int,
|
||||
output_unflatten_spec: torch.utils._pytree.TreeSpec,
|
||||
static_input_surface: tuple[Tensor, ...],
|
||||
static_outputs: tuple[Tensor, ...],
|
||||
static_grad_outputs: tuple[Tensor | None, ...],
|
||||
static_grad_inputs: tuple[Tensor, ...],
|
||||
) -> Callable[..., object]:
|
||||
class Graphed(torch.autograd.Function):
|
||||
@staticmethod
|
||||
# pyrefly: ignore [bad-override]
|
||||
def forward(ctx: object, *inputs: Tensor) -> tuple[Tensor, ...]:
|
||||
# At this stage, only the user args may (potentially) be new tensors.
|
||||
for i in range(len_user_args):
|
||||
if static_input_surface[i].data_ptr() != inputs[i].data_ptr():
|
||||
static_input_surface[i].copy_(inputs[i])
|
||||
fwd_graph.replay()
|
||||
if not isinstance(static_outputs, tuple):
|
||||
raise RuntimeError("static_outputs must be a tuple")
|
||||
return tuple(o.detach() for o in static_outputs)
|
||||
|
||||
@staticmethod
|
||||
@torch.autograd.function.once_differentiable
|
||||
# pyrefly: ignore [bad-override]
|
||||
def backward(ctx: object, *grads: Tensor) -> tuple[Tensor, ...]:
|
||||
if len(grads) != len(static_grad_outputs):
|
||||
raise RuntimeError(
|
||||
f"Expected {len(static_grad_outputs)} gradients but got {len(grads)}"
|
||||
)
|
||||
for g, grad in zip(static_grad_outputs, grads):
|
||||
if g is not None:
|
||||
if g.data_ptr() != grad.data_ptr():
|
||||
g.copy_(grad)
|
||||
bwd_graph.replay()
|
||||
|
||||
if not isinstance(static_grad_inputs, tuple):
|
||||
raise RuntimeError("static_grad_inputs must be a tuple")
|
||||
return tuple(
|
||||
b.detach() if b is not None else b for b in static_grad_inputs
|
||||
)
|
||||
|
||||
def functionalized(*user_args: object) -> object:
|
||||
# Runs the new autograd function which replays the XPU graphs
|
||||
flatten_user_args = torch.utils._pytree.arg_tree_leaves(*user_args)
|
||||
out = Graphed.apply(*(tuple(flatten_user_args) + module_params))
|
||||
return torch.utils._pytree.tree_unflatten(out, output_unflatten_spec)
|
||||
|
||||
return functionalized
|
||||
|
||||
ret: list[_ModuleOrCallable] = []
|
||||
for i, func in enumerate(callables):
|
||||
graphed = make_graphed_autograd_function(
|
||||
fwd_graphs[i],
|
||||
bwd_graphs[i],
|
||||
per_callable_module_params[i],
|
||||
per_callable_len_user_args[i],
|
||||
per_callable_output_unflatten_spec[i],
|
||||
per_callable_static_input_surfaces[i],
|
||||
per_callable_static_outputs[i],
|
||||
per_callable_static_grad_outputs[i],
|
||||
per_callable_static_grad_inputs[i],
|
||||
)
|
||||
|
||||
if isinstance(func, torch.nn.Module):
|
||||
|
||||
def make_graphed_forward(
|
||||
func: torch.nn.Module,
|
||||
graph_training_state: bool,
|
||||
graphed: Callable[_P, _R],
|
||||
orig_fwd: Callable[_P, _R],
|
||||
) -> Callable[_P, _R]:
|
||||
def new_fwd(*user_args: _P.args, **user_kwargs: _P.kwargs) -> _R:
|
||||
if func.training == graph_training_state:
|
||||
return graphed(*user_args, **user_kwargs)
|
||||
else:
|
||||
return orig_fwd(*user_args, **user_kwargs)
|
||||
|
||||
return new_fwd
|
||||
|
||||
func.forward = make_graphed_forward(
|
||||
func, func.training, graphed, func.forward
|
||||
)
|
||||
ret.append(func)
|
||||
else:
|
||||
ret.append(graphed)
|
||||
|
||||
if just_one_callable:
|
||||
return ret[0]
|
||||
|
||||
return tuple(ret)
|
||||
@@ -0,0 +1,643 @@
|
||||
import collections
|
||||
import contextlib
|
||||
import ctypes
|
||||
import pickle
|
||||
import sys
|
||||
from typing import Any, Literal
|
||||
|
||||
import torch
|
||||
from torch._utils import _augment_memory_snapshot_stack_traces, _dummy_type
|
||||
from torch.types import Device
|
||||
|
||||
from . import _get_device_index, _is_compiled, _lazy_init, is_initialized
|
||||
|
||||
|
||||
if not _is_compiled():
|
||||
# Define dummy base classes
|
||||
torch._C.__dict__["_xpu_XPUAllocator"] = _dummy_type("_xpu_XPUAllocator")
|
||||
torch._C.__dict__["_XPUMemPool"] = _dummy_type("_XPUMemPool")
|
||||
torch._C.__dict__["_xpu_beginAllocateCurrentThreadToPool"] = _dummy_type(
|
||||
"_xpu_beginAllocateCurrentThreadToPool"
|
||||
)
|
||||
torch._C.__dict__["_xpu_endAllocateToPool"] = _dummy_type("_xpu_endAllocateToPool")
|
||||
torch._C.__dict__["_xpu_releasePool"] = _dummy_type("_xpu_releasePool")
|
||||
|
||||
|
||||
def empty_cache() -> None:
|
||||
r"""Release all unoccupied cached memory currently held by the caching
|
||||
allocator so that those can be used in other XPU application.
|
||||
|
||||
.. note::
|
||||
:func:`~torch.xpu.empty_cache` doesn't increase the amount of XPU
|
||||
memory available for PyTorch. However, it may help reduce fragmentation
|
||||
of XPU memory in certain cases.
|
||||
"""
|
||||
if is_initialized():
|
||||
torch._C._xpu_emptyCache()
|
||||
|
||||
|
||||
def reset_peak_memory_stats(device: Device = None) -> None:
|
||||
r"""Reset the "peak" stats tracked by the XPU memory allocator.
|
||||
|
||||
See :func:`~torch.xpu.memory_stats` for details. Peak stats correspond to the
|
||||
`"peak"` key in each individual stat dict.
|
||||
|
||||
Args:
|
||||
device (torch.device or int or str, optional): selected device. Returns
|
||||
statistic for the current device, given by :func:`~torch.xpu.current_device`,
|
||||
if :attr:`device` is ``None`` (default).
|
||||
"""
|
||||
device = _get_device_index(device, optional=True)
|
||||
return torch._C._xpu_resetPeakMemoryStats(device)
|
||||
|
||||
|
||||
def reset_accumulated_memory_stats(device: Device = None) -> None:
|
||||
r"""Reset the "accumulated" (historical) stats tracked by the XPU memory allocator.
|
||||
|
||||
See :func:`~torch.xpu.memory_stats` for details. Accumulated stats correspond to
|
||||
the `"allocated"` and `"freed"` keys in each individual stat dict.
|
||||
|
||||
Args:
|
||||
device (torch.device or int or str, optional): selected device. Returns
|
||||
statistic for the current device, given by :func:`~torch.xpu.current_device`,
|
||||
if :attr:`device` is ``None`` (default).
|
||||
"""
|
||||
device = _get_device_index(device, optional=True)
|
||||
return torch._C._xpu_resetAccumulatedMemoryStats(device)
|
||||
|
||||
|
||||
def memory_stats_as_nested_dict(device: Device = None) -> dict[str, Any]:
|
||||
r"""Return the result of :func:`~torch.xpu.memory_stats` as a nested dictionary."""
|
||||
if not is_initialized():
|
||||
return {}
|
||||
device = _get_device_index(device, optional=True)
|
||||
return torch._C._xpu_memoryStats(device)
|
||||
|
||||
|
||||
def memory_stats(device: Device = None) -> dict[str, Any]:
|
||||
r"""Return a dictionary of XPU memory allocator statistics for a given device.
|
||||
|
||||
The return value of this function is a dictionary of statistics, each of
|
||||
which is a non-negative integer.
|
||||
|
||||
Core statistics:
|
||||
|
||||
- ``"allocated_bytes.{all,large_pool,small_pool}.{current,peak,allocated,freed}"``:
|
||||
amount of allocated memory.
|
||||
- ``"reserved_bytes.{all,large_pool,small_pool}.{current,peak,allocated,freed}"``:
|
||||
amount of reserved memory.
|
||||
- ``"active_bytes.{all,large_pool,small_pool}.{current,peak,allocated,freed}"``:
|
||||
amount of active memory.
|
||||
- ``"requested_bytes.{all,large_pool,small_pool}.{current,peak,allocated,freed}"``:
|
||||
memory requested by client code, compare this with allocated_bytes to check if
|
||||
allocation rounding adds too much overhead.
|
||||
|
||||
For these core statistics, values are broken down as follows.
|
||||
|
||||
Pool type:
|
||||
|
||||
- ``all``: combined statistics across all memory pools.
|
||||
- ``large_pool``: statistics for the large allocation pool (for size >= 1MB allocations).
|
||||
- ``small_pool``: statistics for the small allocation pool (for size < 1MB allocations).
|
||||
|
||||
Metric type:
|
||||
|
||||
- ``current``: current value of this metric.
|
||||
- ``peak``: maximum value of this metric.
|
||||
- ``allocated``: historical total increase in this metric.
|
||||
- ``freed``: historical total decrease in this metric.
|
||||
|
||||
Args:
|
||||
device (torch.device or int or str, optional): selected device. Returns
|
||||
statistics for the current device, given by :func:`~torch.xpu.current_device`,
|
||||
if :attr:`device` is ``None`` (default).
|
||||
"""
|
||||
result = []
|
||||
|
||||
def _recurse_add_to_result(prefix: str, obj: Any) -> None:
|
||||
if isinstance(obj, dict):
|
||||
if len(prefix) > 0:
|
||||
prefix += "."
|
||||
for k, v in obj.items():
|
||||
_recurse_add_to_result(prefix + k, v)
|
||||
else:
|
||||
result.append((prefix, obj))
|
||||
|
||||
stats = memory_stats_as_nested_dict(device=device)
|
||||
_recurse_add_to_result("", stats)
|
||||
result.sort()
|
||||
|
||||
return collections.OrderedDict(result)
|
||||
|
||||
|
||||
def memory_allocated(device: Device = None) -> int:
|
||||
r"""Return the current GPU memory occupied by tensors in bytes for a given device.
|
||||
|
||||
Args:
|
||||
device (torch.device or int or str, optional): selected device. Returns
|
||||
statistic for the current device, given by :func:`~torch.xpu.current_device`,
|
||||
if :attr:`device` is ``None`` (default).
|
||||
|
||||
.. note::
|
||||
This is likely less than the amount shown in `xpu-smi` since some
|
||||
unused memory can be held by the caching allocator and some context
|
||||
needs to be created on GPU.
|
||||
"""
|
||||
return memory_stats(device=device).get("allocated_bytes.all.current", 0)
|
||||
|
||||
|
||||
def max_memory_allocated(device: Device = None) -> int:
|
||||
r"""Return the maximum GPU memory occupied by tensors in bytes for a given device.
|
||||
|
||||
By default, this returns the peak allocated memory since the beginning of
|
||||
this program. :func:`~torch.xpu.reset_peak_memory_stats` can be used to
|
||||
reset the starting point in tracking this metric. For example, these two
|
||||
functions can measure the peak allocated memory usage of each iteration in a
|
||||
training loop.
|
||||
|
||||
Args:
|
||||
device (torch.device or int or str, optional): selected device. Returns
|
||||
statistic for the current device, given by :func:`~torch.xpu.current_device`,
|
||||
if :attr:`device` is ``None`` (default).
|
||||
"""
|
||||
return memory_stats(device=device).get("allocated_bytes.all.peak", 0)
|
||||
|
||||
|
||||
def memory_reserved(device: Device = None) -> int:
|
||||
r"""Return the current GPU memory managed by the caching allocator in bytes for a given device.
|
||||
|
||||
Args:
|
||||
device (torch.device or int or str, optional): selected device. Returns
|
||||
statistic for the current device, given by :func:`~torch.xpu.current_device`,
|
||||
if :attr:`device` is ``None`` (default).
|
||||
"""
|
||||
return memory_stats(device=device).get("reserved_bytes.all.current", 0)
|
||||
|
||||
|
||||
def max_memory_reserved(device: Device = None) -> int:
|
||||
r"""Return the maximum GPU memory managed by the caching allocator in bytes for a given device.
|
||||
|
||||
By default, this returns the peak cached memory since the beginning of this
|
||||
program. :func:`~torch.xpu.reset_peak_memory_stats` can be used to reset
|
||||
the starting point in tracking this metric. For example, these two functions
|
||||
can measure the peak cached memory amount of each iteration in a training
|
||||
loop.
|
||||
|
||||
Args:
|
||||
device (torch.device or int or str, optional): selected device. Returns
|
||||
statistic for the current device, given by :func:`~torch.xpu.current_device`,
|
||||
if :attr:`device` is ``None`` (default).
|
||||
"""
|
||||
return memory_stats(device=device).get("reserved_bytes.all.peak", 0)
|
||||
|
||||
|
||||
def mem_get_info(device: Device = None) -> tuple[int, int]:
|
||||
r"""Return the global free and total GPU memory for a given device.
|
||||
|
||||
Args:
|
||||
device (torch.device or int or str, optional): selected device. Returns
|
||||
statistic for the current device, given by :func:`~torch.xpu.current_device`,
|
||||
if :attr:`device` is ``None`` (default).
|
||||
|
||||
Returns:
|
||||
tuple[int, int]: a tuple of two integers (free_memory, total_memory) in bytes.
|
||||
The first value is the free memory on the device (available across all processes and applications),
|
||||
The second value is the device's total hardware memory capacity.
|
||||
"""
|
||||
_lazy_init()
|
||||
device = _get_device_index(device, optional=True)
|
||||
return torch._C._xpu_getMemoryInfo(device)
|
||||
|
||||
|
||||
def get_per_process_memory_fraction(device: Device = None) -> float:
|
||||
r"""
|
||||
Retrieve the memory fraction currently set for a process on a given XPU device.
|
||||
This fraction represents the portion of the total device memory that
|
||||
the caching allocator is allowed to use. The allowed memory is calculated as:
|
||||
|
||||
.. math:: \text{allowed\_memory} = \text{total\_memory} \times \text{fraction}
|
||||
|
||||
Args:
|
||||
device (torch.device or int or str, optional): selected device. It uses the current device,
|
||||
given by :func:`~torch.xpu.current_device`, if :attr:`device` is ``None`` (default).
|
||||
|
||||
Returns:
|
||||
float: The memory fraction in the range 0.0 to 1.0.
|
||||
"""
|
||||
_lazy_init()
|
||||
device = _get_device_index(device, optional=True)
|
||||
return torch._C._xpu_getMemoryFraction(device)
|
||||
|
||||
|
||||
def set_per_process_memory_fraction(fraction: float, device: Device = None) -> None:
|
||||
r"""
|
||||
Set the memory fraction for a single process on XPU device.
|
||||
This function limits the amount of memory that the caching allocator can allocate
|
||||
on the specified XPU device. The allowed memory is computed as:
|
||||
|
||||
.. math:: \text{allowed\_memory} = \text{total\_memory} \times \text{fraction}
|
||||
|
||||
If the process attempts to allocate more than this allowed memory,
|
||||
an out-of-memory error will be raised by the allocator.
|
||||
|
||||
Arguments:
|
||||
fraction (float): Range: 0~1. Allowed memory equals total_memory * fraction.
|
||||
device (torch.device or int or str, optional): selected device. It uses the current device,
|
||||
given by :func:`~torch.xpu.current_device`, if :attr:`device` is ``None`` (default).
|
||||
|
||||
.. note:: In general, the total available free memory is less than the total capacity.
|
||||
"""
|
||||
_lazy_init()
|
||||
device = _get_device_index(device, optional=True)
|
||||
if not isinstance(fraction, float):
|
||||
raise TypeError("Invalid type for fraction argument, must be `float`")
|
||||
torch._C._xpu_setMemoryFraction(fraction, device)
|
||||
|
||||
|
||||
def memory_snapshot(
|
||||
mempool_id: tuple[int, int] | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
r"""
|
||||
Return a snapshot of the XPU memory allocator state across all devices.
|
||||
Provides detailed information for each memory segment managed by the allocator
|
||||
including its size, owning pool, associated stream, call stack traces, and other relevant attributes.
|
||||
|
||||
Arguments:
|
||||
mempool_id (tuple[int, int] or None, optional): The memory pool id. If None, the default memory pool is used.
|
||||
|
||||
Returns:
|
||||
list[dict[str, Any]]: List of memory segments and their attributes.
|
||||
"""
|
||||
if not is_initialized():
|
||||
return []
|
||||
return torch._C._xpu_memorySnapshot(mempool_id)["segments"]
|
||||
|
||||
|
||||
def _snapshot(device: Device = None, augment_with_fx_traces: bool = False):
|
||||
"""
|
||||
Capture a snapshot of the XPU memory state at the time this function is called.
|
||||
|
||||
The returned snapshot is a dictionary with the following structure.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Snapshot(TypedDict):
|
||||
segments: List[Segment]
|
||||
device_traces: List[List[TraceEntry]]
|
||||
|
||||
|
||||
class Segment(TypedDict):
|
||||
# A Segment represents a contiguous memory region returned by the SYCL runtime.
|
||||
#
|
||||
# All reserved memory is composed of these segments. Segments are
|
||||
# cached and reused by the allocator. When allocations are smaller
|
||||
# than the segment, the segment may be split into multiple Blocks.
|
||||
#
|
||||
# Calling :func:`~torch.xpu.memory.empty_cache` releases segments that are entirely inactive.
|
||||
address: int
|
||||
total_size: int # total size of segment
|
||||
stream: int
|
||||
segment_type: Literal["small", "large"] # 'large' (>1MB)
|
||||
allocated_size: int # size of memory in use
|
||||
active_size: int # size of memory in use or in active_awaiting_free state
|
||||
blocks: List[Block]
|
||||
|
||||
|
||||
class Block(TypedDict):
|
||||
# A sub-region of a Segment, either currently allocated or cached for reuse.
|
||||
size: int
|
||||
requested_size: int # Original requested size (may be smaller than `size`)
|
||||
address: int
|
||||
state: Literal[
|
||||
"active_allocated", # used by a tensor
|
||||
"active_awaiting_free", # waiting for another stream synchronization, then become free
|
||||
"inactive", # free for reuse
|
||||
]
|
||||
frames: List[Frame] # stack trace from where the allocation occurred
|
||||
|
||||
|
||||
class Frame(TypedDict):
|
||||
filename: str
|
||||
line: int
|
||||
name: str
|
||||
# Optional fields when `augment_with_fx_traces=True` and the frame
|
||||
# corresponds to FX-generated code.
|
||||
fx_node_op: str # FX node operation type (e.g., 'call_function', 'output')
|
||||
fx_node_name: str # FX node name (e.g., 'linear', 'relu_1')
|
||||
fx_original_trace: str # Original model source code stack trace
|
||||
|
||||
|
||||
class TraceEntry(TypedDict):
|
||||
# Trace entries are recorded only when :func:`~torch.xpu.memory._record_memory_history` is enabled.
|
||||
action: Literal[
|
||||
"alloc" # memory allocated
|
||||
"free_requested", # received a call to free memory
|
||||
"free_completed", # memory reclaimed and reusable
|
||||
"segment_alloc", # ask SYCL runtime for more memory
|
||||
"segment_free", # called SYCL runtime to return memory to XPU
|
||||
"segment_map", # ask SYCL runtime to map memory
|
||||
"segment_unmap", # called SYCL runtime to unmap memory
|
||||
"snapshot", # snapshot taken
|
||||
"oom", # threw an OOM exception
|
||||
]
|
||||
addr: int # not present for OOM
|
||||
frames: List[Frame]
|
||||
size: int
|
||||
stream: int
|
||||
device_free: int # only present for OOM, the amount of free memory reported by the device
|
||||
|
||||
Arguments:
|
||||
device (torch.device or int or str, optional): selected device. It uses the current device,
|
||||
given by :func:`~torch.xpu.current_device`, if :attr:`device` is ``None`` (default).
|
||||
augment_with_fx_traces (bool, optional): If True, augment stack trace frames with FX debug information
|
||||
that maps generated FX code back to original model source code. This adds the FX-related
|
||||
fields (fx_node_op, fx_node_name, fx_original_trace) to Frame objects. Default is ``False``.
|
||||
|
||||
Returns:
|
||||
The Snapshot dictionary object
|
||||
"""
|
||||
s = torch._C._xpu_memorySnapshot(None)
|
||||
if augment_with_fx_traces:
|
||||
s = _augment_memory_snapshot_stack_traces(s) # type: ignore[assignment, arg-type]
|
||||
return s
|
||||
|
||||
|
||||
def _dump_snapshot(
|
||||
filename: str = "dump_snapshot.pickle", augment_with_fx_traces: bool = False
|
||||
) -> None:
|
||||
"""
|
||||
Save a pickled version of the `torch.memory._snapshot()` dictionary to a file.
|
||||
|
||||
This file can be opened by the interactive snapshot viewer at pytorch.org/memory_viz
|
||||
|
||||
Snapshot file sizes scale with `max_entries` and stack trace depth per entry,
|
||||
with several KB per entry. These can easily be in the GB range for longer running
|
||||
workflows with large `max_entries`.
|
||||
|
||||
Arguments:
|
||||
filename (str, optional): Name of the file to create. Defaults to "dump_snapshot.pickle".
|
||||
augment_with_fx_traces (bool, optional): If True, augment the snapshot with FX debug information
|
||||
before dumping. This maps generated FX code stack traces back to original model
|
||||
source code. Defaults to ``False``.
|
||||
"""
|
||||
s = _snapshot(augment_with_fx_traces=augment_with_fx_traces)
|
||||
|
||||
with open(filename, "wb") as f:
|
||||
pickle.dump(s, f)
|
||||
|
||||
|
||||
def _record_memory_history(
|
||||
enabled: Literal["state", "all"] | None = "all",
|
||||
context: Literal["state", "alloc", "all"] | None = "all",
|
||||
stacks: Literal["python", "all"] = "all",
|
||||
max_entries: int = sys.maxsize,
|
||||
clear_history: bool = False,
|
||||
skip_actions: list[str] | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
Enable recording of stack traces associated with memory allocations, so you can
|
||||
tell what allocated any piece of memory in :func:`~torch.xpu.memory._snapshot()`.
|
||||
|
||||
In addition to keeping stack traces with each current allocation and free,
|
||||
this will also enable recording of a history of all alloc/free events.
|
||||
|
||||
Use :func:`~torch.xpu.memory._snapshot()` to retrieve this information,
|
||||
and the tools in `_memory_viz.py` to visualize snapshots.
|
||||
|
||||
Buffer behavior
|
||||
---------------
|
||||
|
||||
This will store up to `max_entries` instances of `TraceEntry` when enabled.
|
||||
Python trace collection defaults to `sys.maxsize`, meaning long-running
|
||||
or indefinitely running jobs should set a reasonable limit to avoid excessive
|
||||
memory use. Expect each entry to be several KB.
|
||||
|
||||
Longer running workflows or those with smaller `max_entries` values will only
|
||||
store the last accumulated `max_entries` entries, meaning new entries overwrite
|
||||
older entries, reference to ring buffer behavior.
|
||||
|
||||
Latency impact
|
||||
--------------
|
||||
|
||||
The Python trace collection is fast (2us per trace), so you may consider
|
||||
enabling this on production jobs if you anticipate ever having to debug
|
||||
memory issues.
|
||||
|
||||
C++ trace collection is also fast (~50ns/frame), which for many typical programs
|
||||
works out to ~2us per trace, but can vary depending on stack depth.
|
||||
|
||||
Arguments:
|
||||
enabled (Literal["state", "all"], optional):
|
||||
`None`, disable recording memory history.
|
||||
`"state"`, keep information for currently allocated memory.
|
||||
`"all"`, additionally keep a history of all alloc/free calls.
|
||||
Defaults to "all".
|
||||
context (Literal["state", "alloc", "all"], optional):
|
||||
`None`, Do not record any tracebacks.
|
||||
`"state"`, Record tracebacks for currently allocated memory.
|
||||
`"alloc"`, additionally keep tracebacks for alloc calls.
|
||||
`"all"`, additionally keep tracebacks for free calls.
|
||||
Defaults to "all".
|
||||
stacks (Literal["python", "all"], optional):
|
||||
`"python"`, include Python, TorchScript, and inductor frames in tracebacks.
|
||||
`"all"`, additionally include C++ frames.
|
||||
Defaults to "all".
|
||||
max_entries (int, optional): Keep a maximum of `max_entries`
|
||||
alloc/free events in the recorded history recorded.
|
||||
clear_history (bool, optional): Clear history when enabling, defaults to ``False``.
|
||||
skip_actions (list[str], optional): List of action types to skip when recording
|
||||
memory history. This can be used to reduce memory overhead by excluding
|
||||
certain types of events from being recorded. Valid action types are:
|
||||
|
||||
- `"alloc"`: Memory allocation events
|
||||
- `"free_requested"`: Free requests (memory marked for freeing)
|
||||
- `"free_completed"`: Completed free operations (memory actually freed)
|
||||
- `"segment_alloc"`: Segment allocation from SYCL runtime
|
||||
- `"segment_free"`: Segment freed back to XPU via SYCL runtime
|
||||
- `"segment_map"`: Segment map events
|
||||
- `"segment_unmap"`: Segment unmap events
|
||||
- `"snapshot"`: Memory snapshot generation events
|
||||
- `"oom"`: Out-of-memory exceptions
|
||||
|
||||
For example, to skip recording free_requested events:
|
||||
`skip_actions=["free_requested"]`
|
||||
|
||||
Defaults to ``None`` (record all actions).
|
||||
"""
|
||||
torch._C._xpu_recordMemoryHistory(
|
||||
enabled,
|
||||
context,
|
||||
stacks,
|
||||
max_entries,
|
||||
clear_history,
|
||||
skip_actions if skip_actions is not None else [],
|
||||
)
|
||||
|
||||
|
||||
class _XPUAllocator:
|
||||
r"""Wrapper over internal XPU memory allocators."""
|
||||
|
||||
def __init__(self, allocator: torch._C._xpu_XPUAllocator):
|
||||
self._allocator = allocator
|
||||
|
||||
def allocator(self):
|
||||
return self._allocator
|
||||
|
||||
|
||||
class XPUPluggableAllocator(_XPUAllocator):
|
||||
r"""
|
||||
XPU memory allocator loaded dynamically from a shared library.
|
||||
|
||||
This lets users provide custom allocation and free functions implemented
|
||||
in a separate shared library. The allocator is registered and could become
|
||||
available for use via :func:`~torch.xpu.memory.change_current_allocator`.
|
||||
|
||||
Arguments:
|
||||
path_to_lib_file (str):
|
||||
Filesystem path to the shared library file containing the allocation
|
||||
and free functions.
|
||||
alloc_fn_name (str):
|
||||
Name of the allocation function exported from the shared library.
|
||||
The function must have the signature:
|
||||
|
||||
``void* alloc_fn(size_t size, int device, sycl::queue* queue);``
|
||||
|
||||
free_fn_name (str):
|
||||
Name of the free function exported from the shared library.
|
||||
The function must have the signature:
|
||||
|
||||
``void free_fn(void* ptr, size_t size, int device, sycl::queue* queue);``
|
||||
"""
|
||||
|
||||
def __init__(self, path_to_lib_file: str, alloc_fn_name: str, free_fn_name: str):
|
||||
allocator_lib = ctypes.CDLL(path_to_lib_file)
|
||||
|
||||
alloc_fn_ptr = getattr(allocator_lib, alloc_fn_name)
|
||||
free_fn_ptr = getattr(allocator_lib, free_fn_name)
|
||||
|
||||
alloc_fn_addr = ctypes.cast(alloc_fn_ptr, ctypes.c_void_p).value
|
||||
free_fn_addr = ctypes.cast(free_fn_ptr, ctypes.c_void_p).value
|
||||
|
||||
if alloc_fn_addr is None or free_fn_addr is None:
|
||||
raise RuntimeError(
|
||||
"Failed to load allocator symbols from the shared library."
|
||||
)
|
||||
|
||||
self._allocator = torch._C._xpu_customAllocator(alloc_fn_addr, free_fn_addr)
|
||||
|
||||
|
||||
def change_current_allocator(allocator: _XPUAllocator) -> None:
|
||||
r"""Change the currently used memory allocator to be the one provided.
|
||||
|
||||
.. note::
|
||||
If the current allocator has already been used/initialized, this function will error.
|
||||
|
||||
Arguments:
|
||||
allocator (torch.xpu.memory._XPUAllocator): allocator to be set as the active one.
|
||||
"""
|
||||
torch._C._xpu_changeCurrentAllocator(allocator.allocator())
|
||||
|
||||
|
||||
def _get_current_allocator() -> _XPUAllocator:
|
||||
r"""Return the allocator being currently used.
|
||||
|
||||
Returns:
|
||||
_XPUAllocator: the allocator being currently used.
|
||||
"""
|
||||
return _XPUAllocator(torch._C._xpu_getAllocator())
|
||||
|
||||
|
||||
class MemPool(torch._C._XPUMemPool):
|
||||
r"""MemPool represents a pool of memory in a caching allocator. Currently,
|
||||
it's just the ID of the pool object maintained in the XPUCachingAllocator.
|
||||
|
||||
Args:
|
||||
allocator(torch._C._xpu_XPUAllocator, optional): a
|
||||
torch._C._xpu_XPUAllocator object that can be used to
|
||||
define how memory gets allocated in the pool. If :attr:`allocator`
|
||||
is ``None`` (default), memory allocation follows the default/
|
||||
current configuration of the XPUCachingAllocator.
|
||||
use_on_oom(bool): a bool that indicates if this pool can be used
|
||||
as a last resort if a memory allocation outside of the pool fails due
|
||||
to Out Of Memory. This is ``False`` by default.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
allocator: torch._C._xpu_XPUAllocator | None = None,
|
||||
use_on_oom: bool = False,
|
||||
):
|
||||
super().__init__(allocator, True, use_on_oom)
|
||||
|
||||
@property
|
||||
def id(self) -> tuple[int, int]:
|
||||
r"""Returns the ID of this pool as a tuple of two ints."""
|
||||
return super().id
|
||||
|
||||
@property
|
||||
def allocator(self) -> torch._C._xpu_XPUAllocator | None:
|
||||
r"""Returns the allocator this MemPool routes allocations to."""
|
||||
return super().allocator
|
||||
|
||||
def use_count(self) -> int:
|
||||
r"""Returns the reference count of this pool."""
|
||||
return super().use_count()
|
||||
|
||||
def snapshot(self):
|
||||
r"""Return a snapshot of the XPU memory allocator pool state across all
|
||||
devices.
|
||||
|
||||
Interpreting the output of this function requires familiarity with the
|
||||
memory allocator internals.
|
||||
"""
|
||||
snapshot = torch.xpu.memory_snapshot(self.id)
|
||||
return snapshot
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def use_mem_pool(pool: MemPool, device: "Device" = None):
|
||||
r"""A context manager that routes allocations to a given pool.
|
||||
|
||||
Args:
|
||||
pool(torch.xpu.MemPool): a :class:`MemPool` object to be made active so that
|
||||
allocations route to this pool.
|
||||
device (torch.device or int, optional): selected device. Uses :class:`MemPool on
|
||||
the current device, given by :func:`~torch.xpu.current_device`,
|
||||
if :attr:`device` is ``None`` (default).
|
||||
|
||||
.. note::
|
||||
This context manager makes only current thread's allocations route to
|
||||
the given pool. If a new thread is spawned inside the context manager
|
||||
(e.g. by calling backward) the allocations in that thread will not
|
||||
route to the given pool.
|
||||
"""
|
||||
device_index = (
|
||||
torch.xpu.current_device() if device is None else _get_device_index(device)
|
||||
)
|
||||
torch._C._xpu_beginAllocateCurrentThreadToPool(device_index, pool.id)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
torch._C._xpu_endAllocateToPool(device_index, pool.id)
|
||||
torch._C._xpu_releasePool(device_index, pool.id)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"MemPool",
|
||||
"XPUPluggableAllocator",
|
||||
"change_current_allocator",
|
||||
"empty_cache",
|
||||
"get_per_process_memory_fraction",
|
||||
"max_memory_allocated",
|
||||
"max_memory_reserved",
|
||||
"mem_get_info",
|
||||
"memory_allocated",
|
||||
"memory_reserved",
|
||||
"memory_snapshot",
|
||||
"memory_stats",
|
||||
"memory_stats_as_nested_dict",
|
||||
"reset_accumulated_memory_stats",
|
||||
"reset_peak_memory_stats",
|
||||
"set_per_process_memory_fraction",
|
||||
"use_mem_pool",
|
||||
]
|
||||
@@ -0,0 +1,176 @@
|
||||
# mypy: allow-untyped-defs
|
||||
from collections.abc import Iterable
|
||||
|
||||
import torch
|
||||
from torch import Tensor
|
||||
|
||||
from . import _lazy_call, _lazy_init, current_device, device_count, is_initialized
|
||||
|
||||
|
||||
def get_rng_state(device: int | str | torch.device = "xpu") -> Tensor:
|
||||
r"""Return the random number generator state of the specified GPU as a ByteTensor.
|
||||
|
||||
Args:
|
||||
device (torch.device or int, optional): The device to return the RNG state of.
|
||||
Default: ``'xpu'`` (i.e., ``torch.device('xpu')``, the current XPU device).
|
||||
|
||||
.. warning::
|
||||
This function eagerly initializes XPU.
|
||||
"""
|
||||
_lazy_init()
|
||||
if isinstance(device, str):
|
||||
device = torch.device(device)
|
||||
elif isinstance(device, int):
|
||||
device = torch.device("xpu", device)
|
||||
idx = device.index
|
||||
if idx is None:
|
||||
idx = current_device()
|
||||
default_generator = torch.xpu.default_generators[idx]
|
||||
return default_generator.get_state()
|
||||
|
||||
|
||||
def get_rng_state_all() -> list[Tensor]:
|
||||
r"""Return a list of ByteTensor representing the random number states of all devices."""
|
||||
results = [get_rng_state(i) for i in range(device_count())]
|
||||
return results
|
||||
|
||||
|
||||
def set_rng_state(new_state: Tensor, device: int | str | torch.device = "xpu") -> None:
|
||||
r"""Set the random number generator state of the specified GPU.
|
||||
|
||||
Args:
|
||||
new_state (torch.ByteTensor): The desired state
|
||||
device (torch.device or int, optional): The device to set the RNG state.
|
||||
Default: ``'xpu'`` (i.e., ``torch.device('xpu')``, the current XPU device).
|
||||
"""
|
||||
if not is_initialized():
|
||||
with torch._C._DisableFuncTorch():
|
||||
new_state = new_state.clone(memory_format=torch.contiguous_format)
|
||||
|
||||
if isinstance(device, str):
|
||||
device = torch.device(device)
|
||||
elif isinstance(device, int):
|
||||
device = torch.device("xpu", device)
|
||||
|
||||
def cb() -> None:
|
||||
idx = device.index
|
||||
if idx is None:
|
||||
idx = current_device()
|
||||
default_generator = torch.xpu.default_generators[idx]
|
||||
default_generator.set_state(new_state)
|
||||
|
||||
_lazy_call(cb)
|
||||
|
||||
|
||||
def set_rng_state_all(new_states: Iterable[Tensor]) -> None:
|
||||
r"""Set the random number generator state of all devices.
|
||||
|
||||
Args:
|
||||
new_states (Iterable of torch.ByteTensor): The desired state for each device.
|
||||
"""
|
||||
for i, state in enumerate(new_states):
|
||||
set_rng_state(state, i)
|
||||
|
||||
|
||||
def manual_seed(seed: int) -> None:
|
||||
r"""Set the seed for generating random numbers for the current GPU.
|
||||
|
||||
It's safe to call this function if XPU is not available; in that case, it is silently ignored.
|
||||
|
||||
Args:
|
||||
seed (int): The desired seed.
|
||||
|
||||
.. warning::
|
||||
If you are working with a multi-GPU model, this function is insufficient
|
||||
to get determinism. To seed all GPUs, use :func:`manual_seed_all`.
|
||||
"""
|
||||
seed = int(seed)
|
||||
|
||||
def cb() -> None:
|
||||
idx = current_device()
|
||||
default_generator = torch.xpu.default_generators[idx]
|
||||
default_generator.manual_seed(seed)
|
||||
|
||||
_lazy_call(cb, seed=True)
|
||||
|
||||
|
||||
def manual_seed_all(seed: int) -> None:
|
||||
r"""Set the seed for generating random numbers on all GPUs.
|
||||
|
||||
It's safe to call this function if XPU is not available; in that case, it is silently ignored.
|
||||
|
||||
Args:
|
||||
seed (int): The desired seed.
|
||||
"""
|
||||
seed = int(seed)
|
||||
|
||||
def cb() -> None:
|
||||
for i in range(device_count()):
|
||||
default_generator = torch.xpu.default_generators[i]
|
||||
default_generator.manual_seed(seed)
|
||||
|
||||
_lazy_call(cb, seed_all=True)
|
||||
|
||||
|
||||
def seed() -> None:
|
||||
r"""Set the seed for generating random numbers to a random number for the current GPU.
|
||||
|
||||
It's safe to call this function if XPU is not available; in that case, it is silently ignored.
|
||||
|
||||
.. warning::
|
||||
If you are working with a multi-GPU model, this function will only initialize
|
||||
the seed on one GPU. To initialize all GPUs, use :func:`seed_all`.
|
||||
"""
|
||||
|
||||
def cb() -> None:
|
||||
idx = current_device()
|
||||
default_generator = torch.xpu.default_generators[idx]
|
||||
default_generator.seed()
|
||||
|
||||
_lazy_call(cb)
|
||||
|
||||
|
||||
def seed_all() -> None:
|
||||
r"""Set the seed for generating random numbers to a random number on all GPUs.
|
||||
|
||||
It's safe to call this function if XPU is not available; in that case, it is silently ignored.
|
||||
"""
|
||||
|
||||
def cb() -> None:
|
||||
random_seed = 0
|
||||
seeded = False
|
||||
for i in range(device_count()):
|
||||
default_generator = torch.xpu.default_generators[i]
|
||||
if not seeded:
|
||||
default_generator.seed()
|
||||
random_seed = default_generator.initial_seed()
|
||||
seeded = True
|
||||
else:
|
||||
default_generator.manual_seed(random_seed)
|
||||
|
||||
_lazy_call(cb)
|
||||
|
||||
|
||||
def initial_seed() -> int:
|
||||
r"""Return the current random seed of the current GPU.
|
||||
|
||||
.. warning::
|
||||
This function eagerly initializes XPU.
|
||||
"""
|
||||
_lazy_init()
|
||||
idx = current_device()
|
||||
default_generator = torch.xpu.default_generators[idx]
|
||||
return default_generator.initial_seed()
|
||||
|
||||
|
||||
__all__ = [
|
||||
"get_rng_state",
|
||||
"get_rng_state_all",
|
||||
"set_rng_state",
|
||||
"set_rng_state_all",
|
||||
"manual_seed",
|
||||
"manual_seed_all",
|
||||
"seed",
|
||||
"seed_all",
|
||||
"initial_seed",
|
||||
]
|
||||
@@ -0,0 +1,181 @@
|
||||
# mypy: allow-untyped-defs
|
||||
# pylint: disable=useless-parent-delegation
|
||||
from __future__ import annotations
|
||||
|
||||
import ctypes
|
||||
|
||||
import torch
|
||||
from torch._utils import _dummy_type
|
||||
|
||||
|
||||
if not hasattr(torch._C, "_XpuStreamBase"):
|
||||
# Define dummy base classes
|
||||
torch._C.__dict__["_XpuStreamBase"] = _dummy_type("_XpuStreamBase")
|
||||
torch._C.__dict__["_XpuEventBase"] = _dummy_type("_XpuEventBase")
|
||||
|
||||
|
||||
class Stream(torch._C._XpuStreamBase):
|
||||
r"""Wrapper around a XPU stream.
|
||||
|
||||
A XPU stream is a linear sequence of execution that belongs to a specific
|
||||
device, independent from other streams. It supports with statement as a
|
||||
context manager to ensure the operators within the with block are running
|
||||
on the corresponding stream.
|
||||
|
||||
Args:
|
||||
device(torch.device or int, optional): a device on which to allocate
|
||||
the stream. If :attr:`device` is ``None`` (default) or a negative
|
||||
integer, this will use the current device.
|
||||
priority(int, optional): priority of the stream, which can be positive, 0, or negative.
|
||||
A lower number indicates a higher priority. By default, the priority is set to 0.
|
||||
If the value falls outside of the allowed priority range, it will automatically be
|
||||
mapped to the nearest valid priority (lowest for large positive numbers or
|
||||
highest for large negative numbers).
|
||||
"""
|
||||
|
||||
def __new__(cls, device=None, priority=0, **kwargs):
|
||||
# setting device manager is expensive, so we avoid it unless necessary
|
||||
if device is None or ("stream_id" in kwargs and "device_index" in kwargs):
|
||||
return super().__new__(cls, priority=priority, **kwargs)
|
||||
else:
|
||||
with torch.xpu.device(device):
|
||||
return super().__new__(cls, priority=priority, **kwargs)
|
||||
|
||||
def wait_event(self, event: Event | torch.Event) -> None:
|
||||
r"""Make all future work submitted to the stream wait for an event.
|
||||
|
||||
Args:
|
||||
event (Event, torch.Event): an event to wait for.
|
||||
"""
|
||||
event.wait(self)
|
||||
|
||||
def wait_stream(self, stream: Stream | torch.Stream) -> None:
|
||||
r"""Synchronize with another stream.
|
||||
|
||||
All future work submitted to this stream will wait until all kernels
|
||||
submitted to a given stream at the time of call complete.
|
||||
|
||||
Args:
|
||||
stream (Stream, torch.Stream): a stream to synchronize.
|
||||
"""
|
||||
self.wait_event(stream.record_event())
|
||||
|
||||
def record_event(self, event: Event | torch.Event | None = None):
|
||||
r"""Record an event.
|
||||
|
||||
Args:
|
||||
event (Event, torch.Event, optional): event to record. If not given, a new one
|
||||
will be allocated.
|
||||
|
||||
Returns:
|
||||
Recorded event.
|
||||
"""
|
||||
if event is None:
|
||||
event = Event()
|
||||
event.record(self)
|
||||
return event
|
||||
|
||||
def query(self) -> bool:
|
||||
r"""Check if all the work submitted has been completed.
|
||||
|
||||
Returns:
|
||||
A boolean indicating if all kernels in this stream are completed.
|
||||
"""
|
||||
return super().query()
|
||||
|
||||
def synchronize(self) -> None:
|
||||
r"""Wait for all the kernels in this stream to complete."""
|
||||
super().synchronize()
|
||||
|
||||
@property
|
||||
def _as_parameter_(self):
|
||||
return ctypes.c_void_p(self.sycl_queue)
|
||||
|
||||
def __eq__(self, o):
|
||||
if isinstance(o, Stream):
|
||||
return super().__eq__(o)
|
||||
return False
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.sycl_queue, self.device))
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"torch.xpu.Stream(device={self.device} sycl_queue={self.sycl_queue:#x})"
|
||||
|
||||
|
||||
class Event(torch._C._XpuEventBase):
|
||||
r"""Wrapper around a XPU event.
|
||||
|
||||
XPU events are synchronization markers that can be used to monitor the
|
||||
device's progress, and to synchronize XPU streams.
|
||||
|
||||
The underlying XPU events are lazily initialized when the event is first
|
||||
recorded. After creation, only streams on the same device may record the
|
||||
event. However, streams on any device can wait on the event.
|
||||
|
||||
Args:
|
||||
enable_timing (bool, optional): indicates if the event should measure time
|
||||
(default: ``False``)
|
||||
"""
|
||||
|
||||
def __new__(cls, enable_timing=False):
|
||||
return super().__new__(cls, enable_timing=enable_timing)
|
||||
|
||||
def record(self, stream: Stream | torch.Stream | None = None) -> None:
|
||||
r"""Record the event in a given stream.
|
||||
|
||||
Args:
|
||||
stream (Stream, torch.Stream, optional): Uses ``torch.xpu.current_stream()`` if no stream is specified.
|
||||
The stream's device must match the event's device.
|
||||
"""
|
||||
if stream is None:
|
||||
stream = torch.xpu.current_stream()
|
||||
super().record(stream)
|
||||
|
||||
def wait(self, stream: Stream | torch.Stream | None = None) -> None:
|
||||
r"""Make all future work submitted to the given stream wait for this event.
|
||||
|
||||
Args:
|
||||
stream (Stream, torch.Stream, optional): Uses ``torch.xpu.current_stream()`` if no stream is specified.
|
||||
"""
|
||||
if stream is None:
|
||||
stream = torch.xpu.current_stream()
|
||||
super().wait(stream)
|
||||
|
||||
def query(self) -> bool:
|
||||
r"""Check if all work currently captured by event has completed.
|
||||
|
||||
Returns:
|
||||
A boolean indicating if all work currently captured by event has
|
||||
completed.
|
||||
"""
|
||||
return super().query()
|
||||
|
||||
def elapsed_time(self, end_event: Event):
|
||||
r"""Return the time elapsed.
|
||||
|
||||
Time reported in milliseconds after the event was recorded and
|
||||
before the end_event was recorded.
|
||||
|
||||
Args:
|
||||
end_event (Event): the end event.
|
||||
"""
|
||||
return super().elapsed_time(end_event)
|
||||
|
||||
def synchronize(self) -> None:
|
||||
r"""Wait for the event to complete.
|
||||
|
||||
Waits until the completion of all work currently captured in this event.
|
||||
This prevents the CPU thread from proceeding until the event completes.
|
||||
"""
|
||||
super().synchronize()
|
||||
|
||||
@property
|
||||
def _as_parameter_(self):
|
||||
return ctypes.c_void_p(self.sycl_event)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
if self.sycl_event:
|
||||
return f"torch.xpu.Event(sycl_event={self.sycl_event:#x})"
|
||||
else:
|
||||
return "torch.xpu.Event(uninitialized)"
|
||||
Reference in New Issue
Block a user