TensorPlay
latest (dev)
Copy
View Markdown

Latest development documentation · Updated 2026-10-15

Source code for tensorplay

"""
The tensorplay package offers a simple deep-learning framework
designed for educational purposes and small-scale experiments.
It defines a data structure for multidimensional arrays called Tensor,
on which it encapsulates mathematical operations.

It has a CUDA counterpart, that enables you to run your tensor computations
on an NVIDIA GPU with compute capability >= 3.0.
"""

import builtins
import ctypes
from enum import IntEnum
from math import e, inf, nan, pi  # noqa: F401
import glob
import importlib
import inspect
import os
import sys
import types
import threading
from typing import (
    Any as _Any,
    TYPE_CHECKING,
)
from typing_extensions import TypeIs as _TypeIs

# Generated at build time by tools/generate_tensorplay_version.py (CMake
# generated version module.
from tensorplay.version import __version__ as __version__

# The OpenMP runtime parks its worker team only after a fixed spin-out, which
# defaults to several milliseconds of full-core spinning per worker. Op
# sequences reach the next region far sooner than that, so a running process
# keeps every worker core hot even when each region is tiny. A ~100us window
# still spans back-to-back regions (measured wall-clock identical) while
# letting idle workers sleep between bursts. Explicit user settings win.
if "GOMP_SPINCOUNT" not in os.environ and "OMP_WAIT_POLICY" not in os.environ:
    os.environ["GOMP_SPINCOUNT"] = "10000"


# -------------------------------------------------------------------------
# DLL Loading (Windows)
# -------------------------------------------------------------------------
if sys.platform == 'win32':
    def _load_dll_libraries():
        # Adapted from TensorPlay
        import sysconfig
        
        # Helper to add DLL directory safely
        def _add_dll_directory(path):
            if os.path.exists(path):
                try:
                    os.add_dll_directory(path)
                except (OSError, AttributeError):
                    pass
        
        # 1. Package's own lib directory (p10.dll, tpx.dll, stax.dll, dnnl.dll, etc.)
        package_lib_path = os.path.join(os.path.dirname(__file__), 'lib')
        _add_dll_directory(package_lib_path)
        # Add to PATH immediately as fallback
        os.environ["PATH"] = package_lib_path + ";" + os.environ["PATH"]
        
        # 2. Package's root directory (sometimes DLLs are here)
        _add_dll_directory(os.path.dirname(__file__))

        # 3. Conda/Python Library/bin (for MKL, OneDNN, etc.)
        py_dll_path = os.path.join(sys.exec_prefix, 'Library', 'bin')
        _add_dll_directory(py_dll_path)
        
        # 4. VirtualEnv support
        if sys.exec_prefix != sys.base_exec_prefix:
            base_py_dll_path = os.path.join(sys.base_exec_prefix, "Library", "bin")
            _add_dll_directory(base_py_dll_path)
        
        # 5. User site-packages Library/bin
        userbase = sysconfig.get_config_var('userbase')
        if userbase:
            user_dll_path = os.path.join(userbase, 'Library', 'bin')
            _add_dll_directory(user_dll_path)
            
        # 6. Explicitly load DLLs to ensure dependencies are resolved
        kernel32 = ctypes.WinDLL("kernel32.dll", use_last_error=True)
        with_load_library_flags = hasattr(kernel32, "AddDllDirectory")
        prev_error_mode = kernel32.SetErrorMode(0x0001)

        # Pre-load critical dependencies in order
        package_lib_path = os.path.join(os.path.dirname(__file__), 'lib')
        
        # Order matters! Dependencies first.
        # MKL -> CUDA/cuDNN -> p10 -> tpx -> stax
        
        # 1. Pre-load MKL (if present)
        mkl_dlls = glob.glob(os.path.join(package_lib_path, "mkl_*.dll"))
        # Load mkl_core and mkl_sequential first if they exist
        priority_mkl = ["mkl_core", "mkl_sequential", "mkl_intel_lp64", "mkl_def", "mkl_avx2"]
        sorted_mkl = []
        for name in priority_mkl:
            for dll in mkl_dlls:
                if name in os.path.basename(dll):
                    sorted_mkl.append(dll)
        # Add remaining MKL DLLs
        for dll in mkl_dlls:
            if dll not in sorted_mkl:
                sorted_mkl.append(dll)
                
        # 2. Pre-load CUDA/cuDNN (if present)
        cuda_dlls = glob.glob(os.path.join(package_lib_path, "cudart*.dll")) + \
                    glob.glob(os.path.join(package_lib_path, "cublas*.dll")) + \
                    glob.glob(os.path.join(package_lib_path, "cudnn*.dll")) + \
                    glob.glob(os.path.join(package_lib_path, "curand*.dll"))
                    
        # 3. Core Libraries
        core_dlls = [
            os.path.join(package_lib_path, "p10.dll"),
            os.path.join(package_lib_path, "tpx.dll"),
            os.path.join(package_lib_path, "stax.dll")
        ]
        
        all_dlls = sorted_mkl + cuda_dlls + core_dlls
        
        path_patched = False
        for dll in all_dlls:
            if not os.path.exists(dll):
                continue
                
            if "OpenCL" in dll:
                continue
            
            is_loaded = False
            if with_load_library_flags:
                # LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
                res = kernel32.LoadLibraryExW(dll, None, 0x00001100)
                if res:
                    is_loaded = True
            
            if not is_loaded:
                # Fallback
                if not path_patched:
                    os.environ["PATH"] = package_lib_path + ";" + os.environ["PATH"]
                    path_patched = True
                res = kernel32.LoadLibraryW(dll)
                
        kernel32.SetErrorMode(prev_error_mode)
            
    _load_dll_libraries()
    del _load_dll_libraries

# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# preloads component CUDA wheels when the extension reports a missing SONAME.
# Keep the same behavior here: a normal CPU-only import does not eagerly load
# CUDA, while a CUDA build remains importable from NVIDIA Python wheels.
elif sys.platform.startswith('linux'):
    # The CUDA wheels consolidate their libraries under a per-toolkit-major
    # directory (nvidia/cuNN, e.g. cu13 for the 13.x generation). Derive the
    # directory from the toolkit version baked into the build metadata so the
    # scan follows the wheel generation this package was linked against. A
    # package whose metadata names no toolkit (CPU build) skips the scan; a
    # preloaded object carrying a foreign SONAME satisfies nothing and the
    # extension retry surfaces the honest missing-soname error.
    try:
        from .version import cuda as _tp_cuda_toolkit
    except ImportError:
        _tp_cuda_toolkit = None
    _tp_cuda_wheel_dir = _tp_cuda_toolkit.split('.', 1)[0] if _tp_cuda_toolkit else None

    def _get_cuda_dep_paths(path, lib_folder, lib_name):
        paths = []
        # Exact wheel layout only (nvidia/cuda_runtime, nvidia/cublas, ...).
        # A broad 'nvidia/cu*' glob would also swallow foreign major-version
        # wheels like nvidia/cu13 and dlopen the wrong libcudart SONAME,
        paths.extend(glob.glob(os.path.join(path, 'nvidia', lib_folder, 'lib', lib_name)))
        paths.extend(glob.glob(os.path.join(path, lib_folder, 'lib', lib_name)))
        if _tp_cuda_wheel_dir:
            paths.extend(glob.glob(os.path.join(
                path, 'nvidia', 'cu' + _tp_cuda_wheel_dir, 'lib', lib_name)))
        if not paths and '.so.' in lib_name:
            stem = lib_name.split('.so.', 1)[0]
            paths.extend(glob.glob(os.path.join(path, 'nvidia', lib_folder, 'lib', stem + '.so')))
            paths.extend(glob.glob(os.path.join(path, lib_folder, 'lib', stem + '.so')))
            if _tp_cuda_wheel_dir:
                paths.extend(glob.glob(os.path.join(
                    path, 'nvidia', 'cu' + _tp_cuda_wheel_dir, 'lib', stem + '.so')))
        return paths

    def _preload_cuda_lib(lib_folder, lib_name, required=True):
        for path in sys.path:
            candidates = _get_cuda_dep_paths(path, lib_folder, lib_name)
            if candidates:
                ctypes.CDLL(candidates[0])
                return
        package_lib_path = os.path.join(os.path.dirname(__file__), 'lib')
        candidates = glob.glob(os.path.join(package_lib_path, lib_name))
        if not candidates and '.so.' in lib_name:
            stem = lib_name.split('.so.', 1)[0]
            candidates = glob.glob(os.path.join(package_lib_path, stem + '.so'))
        if candidates:
            ctypes.CDLL(candidates[0])
            return
        if required:
            raise ImportError(f'{lib_name} not found in TensorPlay CUDA dependency paths')

    def _preload_cuda_deps(err=None):
        if err is not None:
            message = str(err)
            # The package later rebinds any() to the tensor reduction.
            cuda_error = builtins.any(
                token in message
                for token in ('libcud', 'libcublas', 'libcurand', 'libnvrtc', 'libnvjpeg')
            )
            if not cuda_error:
                raise err

        # a mixed CUDA installation through cublas' transitive RUNPATH.
        for lib_folder, lib_name, required in (
            ('cublas', 'libcublasLt.so.*[0-9]', True),
            ('cublas', 'libcublas.so.*[0-9]', True),
            ('cudnn', 'libcudnn.so.*[0-9]', True),
            ('cuda_nvrtc', 'libnvrtc.so.*[0-9]', False),
            ('cuda_runtime', 'libcudart.so.*[0-9]', True),
            ('curand', 'libcurand.so.*[0-9]', True),
            # The image IO bindings link nvjpeg; a toolkit without it
            # surfaces the honest missing-soname error on the retry.
            ('nvjpeg', 'libnvjpeg.so.*[0-9]', False),
        ):
            _preload_cuda_lib(lib_folder, lib_name, required=required)

# -------------------------------------------------------------------------
# Worker wait
# -------------------------------------------------------------------------
# How long an idle worker thread keeps polling for the next parallel region
# before it goes to sleep is read by the threading runtime once, when it is
# loaded.  Left unsaid, some builds of the runtime sleep at once: every region
# then starts by waking its workers, and a training step made of a thousand
# short regions spends a fifth of its time doing that.  A few milliseconds of
# polling is the runtime's long-standing default, so it is asked for by name
# unless the environment already says how workers should wait.
if "GOMP_SPINCOUNT" not in os.environ and "OMP_WAIT_POLICY" not in os.environ:
    os.environ["GOMP_SPINCOUNT"] = "300000"

# -------------------------------------------------------------------------
# Core Imports
# -------------------------------------------------------------------------
try:
    from . import _C as _C
except (ImportError, OSError) as _load_error:
    if sys.platform.startswith('linux') and '_preload_cuda_deps' in globals():
        _preload_cuda_deps(_load_error)
        from . import _C as _C
    else:
        raise

if not hasattr(_C, "_log_api_usage_once"):
    _C._log_api_usage_once = lambda *args, **kwargs: None

from ._tensor import Tensor
from ._C import (tensor, DType, Size, Scalar, SymInt, SymBool, SymFloat,
                sym_float, sym_int, sym_not, sym_min, sym_max, sym_ite, sym_sum,
                Device, DeviceType,
                from_dlpack, to_dlpack, from_numpy, frombuffer, set_printoptions,
                default_generator, manual_seed, seed, initial_seed, Generator,
                UntypedStorage,
                is_storage,
                get_rng_state, set_rng_state,
                set_num_threads, get_num_threads, get_thread_num,
                in_parallel_region, get_parallel_info)
from .autograd import (no_grad, enable_grad, set_grad_enabled, is_grad_enabled,
                       inference_mode, is_inference_mode_enabled)

if hasattr(_C, "get_num_interop_threads"):
    from ._C import get_num_interop_threads, set_num_interop_threads
else:
    # The compiled extension predates the interop thread entry points; expose
    # wrappers over the shared thread pool until the extension is rebuilt.
    def get_num_interop_threads():
        """Returns the number of threads used for interop parallelism on CPU.

        The parallel layer runs a single shared thread pool, so this equals
        the number of intraop threads.
        """
        return get_num_threads()

    def set_num_interop_threads(nthreads):
        """Sets the number of threads used for interop parallelism on CPU.

        The parallel layer runs a single shared thread pool, so the request is
        accepted only when it matches the pool size configured via
        ``set_num_threads``; any other size raises RuntimeError.
        """
        if nthreads <= 0:
            raise ValueError("Expected positive number of threads")
        current = get_num_threads()
        if nthreads != current:
            raise RuntimeError(
                f"cannot set number of interop threads to {nthreads}: the "
                f"parallel layer uses a single shared thread pool whose size "
                f"({current}) is configured via set_num_threads before "
                f"parallel work has started")
from .serialization import save, load, inspect_checkpoint
from .serialization import archive as _serialization_archive
from .random import fork_rng, thread_safe_generator

# -------------------------------------------------------------------------
# DType Aliases
# -------------------------------------------------------------------------
device = Device
dtype = DType
uint8 = DType.uint8
int8 = DType.int8
int16 = DType.int16
uint16 = DType.uint16
uint32 = DType.uint32
uint64 = DType.uint64
int32 = DType.int32
int64 = DType.int64
float32 = DType.float32
float64 = DType.float64
float16 = DType.float16
bfloat16 = DType.bfloat16
complex32 = DType.complex32
complex64 = DType.complex64
complex128 = DType.complex128
bcomplex32 = DType.bcomplex32
bool = DType.bool
undefined = DType.undefined
qint8 = DType.qint8
quint8 = DType.quint8
qint32 = DType.qint32
quint2x4 = DType.quint2x4
quint4x2 = DType.quint4x2
quint1x8 = DType.quint1x8
float8_e4m3fn = DType.float8_e4m3fn
float8_e4m3fnuz = DType.float8_e4m3fnuz
float8_e5m2 = DType.float8_e5m2
float8_e5m2fnuz = DType.float8_e5m2fnuz
float8_e8m0fnu = DType.float8_e8m0fnu

half = DType.float16
float = DType.float32
double = DType.float64
short = DType.int16
int = DType.int32
long = DType.int64
cfloat = DType.complex64
cdouble = DType.complex128
chalf = DType.complex32



[docs]
class MemoryFormat(IntEnum):
    CONTIGUOUS = 0
    PRESERVE = 1
    CHANNELS_LAST = 2
    CHANNELS_LAST_3D = 3
    contiguous_format = 0
    preserve_format = 1
    channels_last = 2
    channels_last_3d = 3

    # Printed as the public module attribute, so generated graph code can
    # spell the value back.
    def __repr__(self):
        return f"tensorplay.{('contiguous_format', 'preserve_format', 'channels_last', 'channels_last_3d')[self.value]}"

    __str__ = __repr__




[docs]
class QScheme(IntEnum):
    """Quantization scheme attached to a quantized tensor's quantizer."""

    PER_TENSOR_AFFINE = 0
    PER_CHANNEL_AFFINE = 1
    PER_TENSOR_SYMMETRIC = 2
    PER_CHANNEL_SYMMETRIC = 3
    PER_CHANNEL_AFFINE_FLOAT_QPARAMS = 4
    per_tensor_affine = PER_TENSOR_AFFINE
    per_channel_affine = PER_CHANNEL_AFFINE
    per_tensor_symmetric = PER_TENSOR_SYMMETRIC
    per_channel_symmetric = PER_CHANNEL_SYMMETRIC
    per_channel_affine_float_qparams = PER_CHANNEL_AFFINE_FLOAT_QPARAMS

    def __repr__(self):
        return f"tensorplay.{self.name.lower()}"

    __str__ = __repr__



per_tensor_affine = QScheme.PER_TENSOR_AFFINE
per_channel_affine = QScheme.PER_CHANNEL_AFFINE
per_tensor_symmetric = QScheme.PER_TENSOR_SYMMETRIC
per_channel_symmetric = QScheme.PER_CHANNEL_SYMMETRIC
per_channel_affine_float_qparams = QScheme.PER_CHANNEL_AFFINE_FLOAT_QPARAMS



[docs]
class Layout(IntEnum):
    """

    ``tensor.layout`` returns one of these values; dense tensors report
    STRIDED.
    """

    SPARSE_COO = 0
    SPARSE_CSR = 1
    SPARSE_CSC = 2
    SPARSE_BSR = 3
    SPARSE_BSC = 4
    STRIDED = 5
    sparse_coo = 0
    sparse_csr = 1
    sparse_csc = 2
    sparse_bsr = 3
    sparse_bsc = 4
    strided = 5

    def __repr__(self):
        return f"tensorplay.{self.name.lower()}"

    __str__ = __repr__



sparse_coo = Layout.SPARSE_COO
sparse_csr = Layout.SPARSE_CSR
sparse_csc = Layout.SPARSE_CSC
sparse_bsr = Layout.SPARSE_BSR
sparse_bsc = Layout.SPARSE_BSC
strided = Layout.STRIDED
contiguous_format = MemoryFormat.CONTIGUOUS
preserve_format = MemoryFormat.PRESERVE
channels_last = MemoryFormat.CHANNELS_LAST
channels_last_3d = MemoryFormat.CHANNELS_LAST_3D

# The enum class under its lowercase alias, plus the historical name for the
# contiguous format kept for source compatibility.
memory_format = MemoryFormat

# Link-time capabilities of the compiled backend.
has_lapack = True
has_spectral = True
compiled_with_cxx11_abi = True

def getArtifactLogger(name: str, artifact: str):
    """A logger for one named artifact, separate from the module's own.

    A piece of work that leaves something behind -- a trace, a set of choices,
    a schedule -- is worth being able to look at on its own, without the rest of
    what the module says.  So a logger is named for the artifact rather than for
    the place, and can be turned on and off without turning anything else on or
    off with it.
    """

    import logging

    return logging.getLogger(f"{name}.{artifact}")


__all__ = [
    "Tensor", "tensor", "from_dlpack", "Scalar", "SymInt", "SymBool", "SymFloat",
    "sym_float", "sym_int", "sym_not", "sym_min", "sym_max", "sym_ite", "sym_sum",
    "DeviceType", "device", "dtype", "Size",
    "MemoryFormat", "contiguous_format", "preserve_format", "channels_last", "channels_last_3d",
    "memory_format",
    "has_lapack", "has_spectral", "compiled_with_cxx11_abi",
    "Layout", "sparse_coo", "sparse_csr", "sparse_csc", "sparse_bsr", "sparse_bsc", "strided",
    "uint8", "int8", "int16", "uint16", "uint32", "uint64", "int32", "int64",
    "float16", "bfloat16", "float32", "float64", "complex32", "complex64", "complex128", "bcomplex32", "bool",
    "qint8", "quint8", "qint32", "quint2x4", "quint4x2", "quint1x8", "QScheme", "per_tensor_affine", "per_channel_affine",
    "per_tensor_symmetric", "per_channel_symmetric", "per_channel_affine_float_qparams",
    "half", "float", "double", "short", "int", "long", "cfloat", "cdouble", "chalf",
    "save", "load", "inspect_checkpoint", "as_tensor",
    "quantized_lstm", "quantized_gru",
    "no_grad", "enable_grad", "set_grad_enabled", "is_grad_enabled",
    "is_inference_mode_enabled",
    "allclose", "index",
    "compile", "compiler", "graph", "library",
    "set_num_threads", "get_num_threads", "get_thread_num",
    "set_num_interop_threads", "get_num_interop_threads",
    "in_parallel_region", "get_parallel_info",
    "default_generator", "manual_seed", "seed", "initial_seed", "Generator",
    "UntypedStorage",
    "is_storage",
    "get_rng_state", "set_rng_state", "fork_rng", "thread_safe_generator",
    "DeviceMismatchError",
    "cond", "while_loop",
    "getArtifactLogger",
    "__config__",
]

# Append functional API to __all__
__all__.extend([
    "abs", "acos", "acosh", "adaptive_avg_pool2d",
    "adaptive_avg_pool2d_backward", "adaptive_max_pool2d",
    "adaptive_max_pool2d_backward", "all", "angle", "any", "arange",
    "argmax", "argmin", "asin", "asinh", "atan", "atan2", "atanh",
    "avg_pool2d", "avg_pool2d_backward", "batch_norm",
    "batch_norm_backward", "bernoulli", "cat", "ceil", "chunk", "clamp",
    "clamp_backward", "constant_pad_nd", "constant_pad_nd_backward",
    "conv1d", "conv1d_grad_bias", "conv1d_grad_input",
    "conv1d_grad_weight", "conv2d", "conv2d_grad_bias",
    "conv2d_grad_input", "conv2d_grad_weight", "conv3d",
    "conv3d_grad_bias", "conv3d_grad_input", "conv3d_grad_weight",
    "conv_transpose2d", "conv_transpose2d_grad_bias",
    "conv_transpose2d_grad_input", "conv_transpose2d_grad_weight",
    "conv_transpose3d", "conv_transpose3d_grad_bias",
    "conv_transpose3d_grad_input", "conv_transpose3d_grad_weight", "cos",
    "cosh", "embedding", "embedding_dense_backward", "empty",
    "empty_like", "eq", "exp", "eye", "floor", "full", "full_like", "ge",
    "gelu", "group_norm", "group_norm_backward", "gt", "instance_norm",
    "instance_norm_backward", "layer_norm", "layer_norm_backward", "le",
    "lerp", "linspace", "log", "log_softmax", "logspace", "lt",
    "masked_select", "matmul", "max", "max_pool2d", "max_pool2d_backward",
    "mean", "median", "min", "mm", "mse_loss", "mse_loss_backward", "ne",
    "neg", "nll_loss", "nll_loss_backward", "norm", "normal", "ones",
    "ones_like", "permute", "permute_backward", "poisson", "pow", "prod",
    "rand", "rand_like", "randint", "randint_like", "randn", "randn_like",
    "randperm", "relu", "reshape", "round", "rsqrt", "sigmoid", "sign",
    "silu", "sin", "sinh", "softmax", "split", "sqrt", "square",
    "sparse_compressed_tensor", "sparse_csr_tensor", "sparse_csc_tensor",
    "sparse_bsr_tensor", "sparse_bsc_tensor",
    "squeeze", "squeeze_backward", "stack", "std", "sum", "t", "tan",
    "tanh", "threshold_backward", "transpose", "unbind", "unsqueeze",
    "var", "zeros", "zeros_like",
    "where", "maximum", "minimum", "addcmul", "addcdiv",
    "view_as_real", "view_as_complex", "is_complex",
])

# Please keep this list sorted
# assert __all__ == sorted(__all__)

import functools

from ._C import (
    abs, acos, acosh, adaptive_avg_pool2d, adaptive_avg_pool2d_backward, 
    adaptive_max_pool2d, adaptive_max_pool2d_backward, all, angle, any, 
    arange, argmax, argmin, asin, asinh, atan, atan2, atanh, avg_pool2d, 
    avg_pool2d_backward, batch_norm, batch_norm_backward, bernoulli, cat, 
    ceil, chunk, clamp, clamp_backward, constant_pad_nd, 
    constant_pad_nd_backward, conv1d, conv1d_grad_bias, conv1d_grad_input, 
    conv1d_grad_weight, conv2d, conv2d_grad_bias, conv2d_grad_input, 
    conv2d_grad_weight, conv3d, conv3d_grad_bias, conv3d_grad_input, 
    conv3d_grad_weight, conv_transpose2d, conv_transpose2d_grad_bias, 
    conv_transpose2d_grad_input, conv_transpose2d_grad_weight, 
    conv_transpose3d, conv_transpose3d_grad_bias, 
    conv_transpose3d_grad_input, conv_transpose3d_grad_weight, cos, cosh, 
    embedding, embedding_dense_backward, empty, empty_like, eq, exp, eye, 
    floor, full, full_like, ge, gelu, group_norm, group_norm_backward, gt, 
    instance_norm, instance_norm_backward, layer_norm, 
    layer_norm_backward, le, lerp, linspace, log, log_softmax, logspace, 
    lt, masked_select, matmul, max, max_pool2d, max_pool2d_backward, mean, 
    median, min, mm, mse_loss, mse_loss_backward, ne, neg, nll_loss, 
    nll_loss_backward, norm, normal, ones, ones_like, permute, 
    permute_backward, poisson, pow, prod, rand, rand_like, randint, 
    randint_like, randn, randn_like, randperm, relu, reshape, round, 
    rsqrt, sigmoid, sign, silu, sin, sinh, softmax, split, sqrt, square, 
    squeeze, squeeze_backward, stack, std, sum, t, tan, tanh,
    threshold_backward, transpose, unbind, unsqueeze, var, zeros,
    zeros_like, as_tensor,
)

# Catchable device-mismatch error raised by the C++ core (RuntimeError
# subclass); kept explicit because it is an exception type, not an op.
DeviceMismatchError = _C.DeviceMismatchError

__attr_name, __obj = "", None
for __attr_name in dir(_C):
    if __attr_name[0] != "_" and not __attr_name.endswith("Base"):
        __all__.append(__attr_name)
        __obj = getattr(_C, __attr_name)
        if callable(__obj) or inspect.isclass(__obj):
            if os.getenv("TENSORPLAY_BUILDING_STUBS") == "1":
                continue

            if __obj.__module__ != __name__:
                try:
                    __obj.__module__ = __name__
                except AttributeError:
                    # Fallback: wrap it if it's a function and module wasn't updated
                    # (pybind11 functions are builtin_function_or_method, which
                    # rejects __module__ writes)
                    if callable(__obj):
                         def make_wrapper(f):
                             @functools.wraps(f)
                             def wrapper(*args, **kwargs):
                                 return f(*args, **kwargs)
                             wrapper.__module__ = __name__
                             return wrapper
                         
                         wrapper = make_wrapper(__obj)
                         # Overwrite a name already installed by the native binding.
                         if __attr_name in globals():
                             globals()[__attr_name] = wrapper
            if __attr_name not in globals():
                globals()[__attr_name] = __obj

    elif __attr_name == "TensorBase":
        if hasattr(sys.modules[__name__], __attr_name):
            delattr(sys.modules[__name__], __attr_name)

del __attr_name, __obj

if not TYPE_CHECKING:
    def _import_extension_to_sys_modules(module, memo=None):
        """
        Recursively import submodules of a C extension module into sys.modules.
        """
        if memo is None:
            memo = set()
        if module in memo:
            return
        memo.add(module)
        module_name = module.__name__
        for name in dir(module):
            member = getattr(module, name)
            member_name = getattr(member, "__name__", "")
            if inspect.ismodule(member) and member_name.startswith(module_name):
                sys.modules.setdefault(member_name, member)
                _import_extension_to_sys_modules(member, memo)

    _import_extension_to_sys_modules(_C)
    del _import_extension_to_sys_modules


from .functional import *
from .functional import _assert_async
from .functional import _unsafe_index_put


def unique(input, sorted=True, return_inverse=False, return_counts=False):
    """Return unique values and optionally inverse indices and counts.

    The native op always computes all three outputs; this wrapper selects the
    requested ``return_inverse`` / ``return_counts`` values.
    """
    values, inverse, counts = _C.unique(input, sorted, True, True)
    if return_inverse and return_counts:
        return values, inverse, counts
    if return_counts:
        return values, counts
    if return_inverse:
        return values, inverse
    return values


def _assert(condition: object, message: object) -> None:
    r"""Python's ``assert`` as a call, so graph capture can record it."""
    if type(condition) is not Tensor and overrides.has_tensorplay_function(
        (condition,)
    ):
        return overrides.handle_tensorplay_function(
            _assert, (condition,), condition, message
        )
    if not condition:
        raise AssertionError(message)

from ._shape_funcs import *
from ._composite_funcs import *
from ._einsum import einsum
from .utils.comparison import allclose

from ._ops import ops as ops


def _register_checks() -> None:
    """Keep checks in captured graphs although nothing reads their result."""

    from . import functional
    from .graph.node import _side_effectful_targets

    native = ops.tp
    _side_effectful_targets.update({
        _assert,
        functional._assert_async,
        functional._assert_scalar,
        functional._assert_tensor_metadata,
        functional.sym_constrain_range,
        functional.sym_constrain_range_for_size,
        native._assert_async.default,
        native._assert_async.msg,
        native._assert_scalar.default,
        native._assert_tensor_metadata.default,
        native.sym_constrain_range.default,
        native.sym_constrain_range_for_size.default,
    })


_register_checks()
del _register_checks

from . import _stax
from .compiler._core import compile
from . import compiler
from . import library
from . import profiler

# -------------------------------------------------------------------------
# Submodules
# -------------------------------------------------------------------------
from . import cuda
# Imported after the op names, nn, and cuda are bound: the multiprocessing
# reducers pull in tensorplay.primitives (via nn) which references eager op
# names, so this must not run during partial initialization.
from . import multiprocessing
from . import stax
from . import backends
# Import nn before optim because optim.swa_utils pulls in tensorplay.nn,
# which must be fully initialized by then to avoid a partial-import cycle.
from . import nn
from . import optim
from . import graph
from . import autograd
from . import distributed
from . import utils
from . import __config__

from . import amp

from .amp.autocast_mode import (  # noqa: E402
    autocast_decrement_nesting,
    autocast_increment_nesting,
    clear_autocast_cache,
    get_autocast_cpu_dtype,
    get_autocast_dtype,
    get_autocast_gpu_dtype,
    is_autocast_available,
    is_autocast_cache_enabled,
    is_autocast_enabled,
    set_autocast_cache_enabled,
    set_autocast_dtype,
    set_autocast_enabled,
)

from .amp import (  # noqa: E402
    GradScaler,
    autocast,
    custom_bwd,
    custom_fwd,
)

__all__.extend([
    "GradScaler",
    "amp",
    "cuda",
    "autocast",
    "autocast_decrement_nesting",
    "autocast_increment_nesting",
    "clear_autocast_cache",
    "custom_bwd",
    "custom_fwd",
    "get_autocast_cpu_dtype",
    "get_autocast_dtype",
    "get_autocast_gpu_dtype",
    "is_autocast_available",
    "is_autocast_cache_enabled",
    "is_autocast_enabled",
    "set_autocast_cache_enabled",
    "set_autocast_dtype",
    "set_autocast_enabled",
])


[docs]
def typename(obj: _Any, /) -> str:
    """
    String representation of the type of an object.

    This function returns a fully qualified string representation of an object's type.
    Args:
        obj (object): The object whose type to represent
    Returns:
        str: the type of the object `o`
    Example:
        >>> x = tensorplay.tensor([1, 2, 3])
        >>> tensorplay.typename(x)
        'tensorplay.LongTensor'
        >>> tensorplay.typename(tensorplay.nn.Parameter)
        'tensorplay.nn.parameter.Parameter'
    """
    if isinstance(obj, tensorplay.Tensor):
        return obj.type()

    module = getattr(obj, "__module__", "") or ""
    qualname = ""

    if hasattr(obj, "__qualname__"):
        qualname = obj.__qualname__
    elif hasattr(obj, "__name__"):
        qualname = obj.__name__
    else:
        module = obj.__class__.__module__ or ""
        qualname = obj.__class__.__qualname__

    if module in {"", "builtins"}:
        return qualname
    return f"{module}.{qualname}"




[docs]
def is_tensor(obj: _Any, /) -> _TypeIs["tensorplay.Tensor"]:
    r"""Returns True if `obj` is a TensorPlay tensor.

    Note that this function is simply doing ``isinstance(obj, Tensor)``.
    Using that ``isinstance`` check is better for type checking with mypy,
    and more explicit - so it's recommended to use that instead of
    ``is_tensor``.

    Args:
        obj (object): Object to test
    Example::

        >>> x = tensorplay.tensor([1, 2, 3])
        >>> tensorplay.is_tensor(x)
        True

    """
    return isinstance(obj, tensorplay.Tensor)




[docs]
def as_tensor(data, dtype=None, device=None):
    r"""Convert ``data`` into a tensor, sharing storage when possible.

    If ``data`` is already a tensor with the requested dtype and device, it is
    returned as-is (no copy).  Otherwise it is converted to the requested
    dtype and device.

    Args:
        data (tensor, list, or scalar): Initial data for the tensor.
        dtype (:class:`tensorplay.DType`, optional): the desired data type of
            the returned tensor.
        device (:class:`tensorplay.Device`, str, optional): the device of the
            constructed tensor.

    Example::

        >>> x = tensorplay.tensor([1.0, 2.0])
        >>> tensorplay.as_tensor(x) is x
        True
        >>> tensorplay.as_tensor([0, 1, 2], dtype=tensorplay.int64).dtype == tensorplay.int64
        True
    """
    if isinstance(data, tensorplay.Tensor):
        if dtype is not None and data.dtype != dtype:
            data = data.to(dtype)
        if device is not None:
            if not isinstance(device, tensorplay.device):
                device = tensorplay.device(device)
            if data.device != device:
                data = data.to(device)
        return data
    return tensor(data, dtype=dtype, device=device)



_GLOBAL_DEVICE_CONTEXT = threading.local()

# thread-local mode stack semantics); _GLOBAL_DEVICE_CONTEXT is kept for API



[docs]
def get_default_device() -> "tensorplay.device":
    r"""Gets the default ``tensorplay.Tensor`` to be allocated on ``device``"""
    return _C.get_default_device()




[docs]
def set_default_device(device: Device) -> None:
    """Sets the default ``tensorplay.Tensor`` to be allocated on ``device``.  This
    does not affect factory function calls which are called with an explicit
    ``device`` argument.  Factory calls will be performed as if they
    were passed ``device`` as an argument.

    To only temporarily change the default device instead of setting it
    globally, use ``with tensorplay.device(device):`` instead.

    The default device is initially ``cpu``.  If you set the default tensor
    device to another device (e.g., ``cuda``) without a device index, tensors
    will be allocated on whatever the current device for the device type,
    even after :func:`tensorplay.cuda.set_device` is called.

    .. warning::

        This function imposes a slight performance cost on every Python
        call to the tensorplay API (not just factory functions).

    .. note::

        This doesn't affect functions that create tensors that share the same memory as the input, like:
        :func:`tensorplay.from_numpy` and :func:`tensorplay.frombuffer`

    Args:
        device (:class:`tensorplay.device`, str, int, or None): the device to set as
            default, or ``None`` to clear the override. An integer is
            interpreted as an index for the current accelerator.

    Example::

        >>> # xdoctest: +SKIP("requires cuda, changes global state")
        >>> tensorplay.get_default_device()
        device(type='cpu')
        >>> tensorplay.set_default_device('cuda')  # current device is 0
        >>> tensorplay.get_default_device()
        device(type='cuda', index=0)
        >>> tensorplay.set_default_device('cuda')
        >>> tensorplay.cuda.set_device('cuda:1')  # current device is 1
        >>> tensorplay.get_default_device()
        device(type='cuda', index=1)
        >>> tensorplay.set_default_device('cuda:1')
        >>> tensorplay.get_default_device()
        device(type='cuda', index=1)

    """
    if isinstance(device, str):
        device = Device(device)
    elif isinstance(device, builtins.int):
        # An integer is interpreted as an index for the current accelerator
        # (CUDA is TensorPlay's accelerator device type).
        device = Device(DeviceType.CUDA, device)
    _C._set_default_device(device)



_default_dtype: DType = float32



[docs]
def get_default_dtype() -> DType:
    """Returns the current default floating point dtype (float32 initially,
    changed by :func:`set_default_dtype`)."""
    return _default_dtype




[docs]
def set_default_dtype(d: DType, /) -> None:
    r"""

    Sets the default floating point dtype to :attr:`d`. Supports floating point dtype
    as inputs. Other dtypes will cause tensorplay to raise an exception.

    When TensorPlay is initialized its default floating point dtype is float32,
    and the intent of set_default_dtype(float64) is to facilitate NumPy-like
    type inference. The default floating point dtype is used to:

    1. Implicitly determine the default complex dtype. When the default floating type is float16,
       the default complex dtype is complex32. For float32, the default complex dtype is complex64.
       For float64, it is complex128. For bfloat16, an exception will be raised because
       there is no corresponding complex type for bfloat16.
    2. Infer the dtype for tensors constructed using Python floats or complex Python
       numbers. See examples below.
    3. Determine the result of type promotion between bool and integer tensors and
       Python floats and complex Python numbers.

    Args:
        d (:class:`tensorplay.dtype`): the floating point dtype to make the default.

    Example:
        >>> # xdoctest: +SKIP("Other tests may have changed the default type. Can we reset it?")
        >>> # initial default for floating point is float32
        >>> # Python floats are interpreted as float32
        >>> tensorplay.tensor([1.2, 3]).dtype
        tensorplay.float32
        >>> # initial default for floating point is complex64
        >>> # Complex Python numbers are interpreted as complex64
        >>> tensorplay.tensor([1.2, 3j]).dtype
        tensorplay.complex64

        >>> tensorplay.set_default_dtype(tensorplay.float64)
        >>> # Python floats are now interpreted as float64
        >>> tensorplay.tensor([1.2, 3]).dtype  # a new floating point tensor
        tensorplay.float64
        >>> # Complex Python numbers are now interpreted as complex128
        >>> tensorplay.tensor([1.2, 3j]).dtype  # a new complex tensor
        tensorplay.complex128

        >>> tensorplay.set_default_dtype(tensorplay.float16)
        >>> # Python floats are now interpreted as float16
        >>> tensorplay.tensor([1.2, 3]).dtype  # a new floating point tensor
        tensorplay.float16
        >>> # Complex Python numbers are now interpreted as complex128
        >>> tensorplay.tensor([1.2, 3j]).dtype  # a new complex tensor
        tensorplay.complex32

    """
    _C._set_default_dtype(d)
    global _default_dtype
    _default_dtype = d




[docs]
def use_deterministic_algorithms(
    mode: builtins.bool,
    *,
    warn_only: builtins.bool = False,
) -> None:
    r"""Sets whether TensorPlay operations must use "deterministic"
    algorithms. That is, algorithms which, given the same input, and when
    run on the same software and hardware, always produce the same output.
    When enabled, operations will use deterministic algorithms when available,
    and if only nondeterministic algorithms are available they will throw a
    :class:`RuntimeError` when called.

    .. note:: This setting alone is not always enough to make an application
        reproducible. Refer to :ref:`reproducibility` for more information.

    .. note:: :func:`tensorplay.set_deterministic_debug_mode` offers an alternative
        interface for this feature.

    Note that deterministic operations tend to have worse performance than
    nondeterministic operations.

    .. note::

        This flag does not detect or prevent nondeterministic behavior caused
        by calling an inplace operation on a tensor with an internal memory
        overlap or by giving such a tensor as the :attr:`out` argument for an
        operation. In these cases, multiple writes of different data may target
        a single memory location, and the order of writes is not guaranteed.

    Args:
        mode (:class:`bool`): If True, makes potentially nondeterministic
            operations switch to a deterministic algorithm or throw a runtime
            error. If False, allows nondeterministic operations.

    Keyword args:
        warn_only (:class:`bool`, optional): If True, operations that do not
            have a deterministic implementation will throw a warning instead of
            an error. Default: ``False``

    Example::

        >>> # xdoctest: +SKIP
        >>> tensorplay.use_deterministic_algorithms(True)
    """
    # This flag has no compiler-specific counterpart.
    _C._set_deterministic_algorithms(mode, warn_only=warn_only)




[docs]
def are_deterministic_algorithms_enabled() -> builtins.bool:
    r"""Returns True if the global deterministic flag is turned on. Refer to
    :func:`tensorplay.use_deterministic_algorithms` documentation for more details.
    """
    return _C._get_deterministic_algorithms()




[docs]
def is_deterministic_algorithms_warn_only_enabled() -> builtins.bool:
    r"""Returns True if the global deterministic flag is set to warn only.
    Refer to :func:`tensorplay.use_deterministic_algorithms` documentation for more
    details.
    """
    return _C._get_deterministic_algorithms_warn_only()




[docs]
def set_deterministic_debug_mode(debug_mode: builtins.int | str) -> None:
    r"""Sets the debug mode for deterministic operations.

    .. note:: This is an alternative interface for
        :func:`tensorplay.use_deterministic_algorithms`. Refer to that function's
        documentation for details about affected operations.

    Args:
        debug_mode(str or int): If "default" or 0, don't error or warn on
            nondeterministic operations. If "warn" or 1, warn on
            nondeterministic operations. If "error" or 2, error on
            nondeterministic operations.
    """

    # NOTE: builtins.int is used here because int in this scope resolves
    # to tensorplay.int
    if not isinstance(debug_mode, (builtins.int, str)):
        raise TypeError(f"debug_mode must be str or int, but got {type(debug_mode)}")

    if isinstance(debug_mode, str):
        if debug_mode == "default":
            debug_mode = 0
        elif debug_mode == "warn":
            debug_mode = 1
        elif debug_mode == "error":
            debug_mode = 2
        else:
            raise RuntimeError(
                "invalid value of debug_mode, expected one of `default`, "
                f"`warn`, `error`, but got {debug_mode}"
            )

    if debug_mode == 0:
        _C._set_deterministic_algorithms(False)
    elif debug_mode == 1:
        _C._set_deterministic_algorithms(True, warn_only=True)
    elif debug_mode == 2:
        _C._set_deterministic_algorithms(True)
    else:
        raise RuntimeError(
            f"invalid value of debug_mode, expected 0, 1, or 2, but got {debug_mode}"
        )




[docs]
def get_deterministic_debug_mode() -> builtins.int:
    r"""Returns the current value of the debug mode for deterministic
    operations. Refer to :func:`tensorplay.set_deterministic_debug_mode`
    documentation for more details.
    """

    if _C._get_deterministic_algorithms():
        if _C._get_deterministic_algorithms_warn_only():
            return 1
        else:
            return 2
    else:
        return 0




[docs]
def get_float32_matmul_precision() -> str:
    r"""Returns the current value of float32 matrix multiplication precision. Refer to
    :func:`tensorplay.set_float32_matmul_precision` documentation for more details.
    """
    return _C.get_float32_matmul_precision()




[docs]
def set_float32_matmul_precision(precision: str) -> None:
    r"""Sets the internal precision of float32 matrix multiplications.

    Running float32 matrix multiplications in lower precision may significantly increase
    performance, and in some programs the loss of precision has a negligible impact.

    Supports three settings:

        * "highest", float32 matrix multiplications use the float32 datatype (24 mantissa
          bits with 23 bits explicitly stored) for internal computations.
        * "high", float32 matrix multiplications either use the TensorFloat32 datatype (10
          mantissa bits explicitly stored) or treat each float32 number as the sum of two bfloat16 numbers
          (approximately 16 mantissa bits with 14 bits explicitly stored), if the appropriate fast matrix multiplication
          algorithms are available.  Otherwise float32 matrix multiplications are computed
          as if the precision is "highest".  See below for more information on the bfloat16
          approach.
        * "medium", float32 matrix multiplications use the bfloat16 datatype (8 mantissa
          bits with 7 bits explicitly stored) for internal computations, if a fast matrix multiplication algorithm
          using that datatype internally is available. Otherwise float32
          matrix multiplications are computed as if the precision is "high".

    .. [Henry2019] http://arxiv.org/abs/1904.06376

    .. note::

        This does not change the output dtype of float32 matrix multiplications,
        it controls how the internal computation of the matrix multiplication is performed.

    .. note::

        This does not change the precision of convolution operations. Other flags,
        like `tensorplay.backends.cudnn.allow_tf32`, may control the precision of convolution
        operations.

    .. note::

        This flag currently only affects one native device type: CUDA.
        If "high" or "medium" are set then the TensorFloat32 datatype will be used
        when computing float32 matrix multiplications, equivalent to setting
        `tensorplay.backends.cuda.matmul.allow_tf32 = True`. When "highest" (the default)
        is set then the float32 datatype is used for internal computations, equivalent
        to setting `tensorplay.backends.cuda.matmul.allow_tf32 = False`.

    Args:
        precision(str): can be set to "highest" (default), "high", or "medium" (see above).

    """
    if not isinstance(precision, str):
        raise TypeError("set_float32_matmul_precision expects a str, "
                        f"but got {type(precision)}")
    _C._set_float32_matmul_precision(precision)



__all__.extend([
    "inference_mode",
    "get_default_dtype",
    "set_default_dtype",
    "get_default_device",
    "set_default_device",
    "use_deterministic_algorithms",
    "are_deterministic_algorithms_enabled",
    "is_deterministic_algorithms_warn_only_enabled",
    "set_deterministic_debug_mode",
    "get_deterministic_debug_mode",
    "get_float32_matmul_precision",
    "set_float32_matmul_precision",
    "get_printoptions",
    "printoptions",
])

newaxis: None = None

__all__.extend(["e", "pi", "nan", "inf", "newaxis"])

from tensorplay._tensor import Tensor

# The _tensor_classes set is initialized by the call to initialize_python_bindings.
_tensor_classes: set[type[Tensor]] = set()

import tensorplay

__all__.extend(
    name for name in dir(tensorplay) if isinstance(getattr(tensorplay, name), tensorplay.dtype)
)

# needs to be after the above c++ bindings so we can overwrite from Python side
from tensorplay import functional as functional
from tensorplay.functional import *

# Underscore ops are skipped by the star-import above; expose them
# explicitly so tensorplay._standard_gamma / _sample_dirichlet /
# _safe_softmax resolve.
from tensorplay.functional import (
    _standard_gamma as _standard_gamma,
    _sample_dirichlet as _sample_dirichlet,
    _safe_softmax as _safe_softmax,
)

# Keep package-level dtype aliases and composite signatures after importing
# the generated function surface.
chalf = DType.complex32
from tensorplay._shape_funcs import (
    block_diag as block_diag,
    broadcast_tensors as broadcast_tensors,
    meshgrid as meshgrid,
    tensordot as tensordot,
)

# Re-pin the einsum shim: functional.py's thin wrapper predates the sublist
# quantile/nanquantile/histogram need the same treatment: the generated
# functional.py wrappers forward a raw Python-number `q` (the _C binding
# requires a Tensor) and drop `histogram`'s `range` keyword, so the
# hand-written _composite_funcs versions (scalar-q -> input-dtype Tensor
from ._einsum import einsum as einsum
from ._composite_funcs import quantile as quantile
from ._composite_funcs import nanquantile as nanquantile
from ._composite_funcs import histogram as histogram
from ._finfo import finfo, iinfo
from ._tensor_str import (
    get_printoptions as get_printoptions,
    printoptions as printoptions,
    set_printoptions as set_printoptions,
)
# Python's ``import *`` intentionally omits underscore-prefixed names, but
# only generated ``_foreach_*`` wrappers; their implementation is still the
# native dispatcher/backend and this block does not introduce a Python
# composite operator.  ``_amp_*`` dispatcher hooks follow the same rule
for _foreach_name in dir(functional):
    if (_foreach_name.startswith("_foreach_") or _foreach_name.startswith("_amp_")
            or _foreach_name.startswith("_fused_")
            or _foreach_name.startswith("_nested_")):
        globals()[_foreach_name] = getattr(functional, _foreach_name)
        if _foreach_name not in __all__:
            __all__.append(_foreach_name)
del _foreach_name


# -------------------------------------------------------------------------
# ``from tensorplay.functional import *`` above, which shadows earlier defs).
#
# reduction returning a named tuple with ``values``/``indices``, and the
# reduction faces only, so the binary face and the named-tuple contract are
# restored here at the package boundary.
# -------------------------------------------------------------------------
import collections as _collections

from ._return_types import (  # noqa: E402
    aminmax_return_type as _aminmax_return_type,
    cummax_return_type as _cummax_return_type,
    cummin_return_type as _cummin_return_type,
    kthvalue_return_type as _kthvalue_return_type,
    max_return_type as _max_return_type,
    median_return_type as _median_return_type,
    min_return_type as _min_return_type,
    mode_return_type as _mode_return_type,
    nanmedian_return_type as _nanmedian_return_type,
    sort_return_type as _sort_return_type,
    topk_return_type as _topk_return_type,
)



[docs]
def max(input, *args, dim=None, keepdim=False):
    # ``args`` carries the elementwise partner of the two-argument form, or
    # the ``(dim, keepdim)`` pair when a recorded graph replays the native
    # call form.  ``dim``/``keepdim`` stay keyword-only for the public API.
    # A bare integer in the second slot is the reduction dimension.
    if len(args) == 2:
        result = _C.max(input, args[0], args[1])
        if not isinstance(result, tuple):
            return result
        return _max_return_type(*result)
    other = args[0] if args else None
    if other is not None and not isinstance(other, builtins.int):
        return _C.maximum(input, other)
    if other is not None:
        dim = other
    if dim is not None:
        result = _C.max(input, dim, keepdim)
        # A graph value stands for the whole multi-output call: consumers
        # split the outputs downstream, so the named tuple is only
        # constructed for eager results.
        if not isinstance(result, tuple):
            return result
        return _max_return_type(*result)
    return functional.max(input)




[docs]
def min(input, *args, dim=None, keepdim=False):
    # Same dual-form convention as max above.
    if len(args) == 2:
        result = _C.min(input, args[0], args[1])
        if not isinstance(result, tuple):
            return result
        return _min_return_type(*result)
    other = args[0] if args else None
    if other is not None and not isinstance(other, builtins.int):
        return _C.minimum(input, other)
    if other is not None:
        dim = other
    if dim is not None:
        result = _C.min(input, dim, keepdim)
        if not isinstance(result, tuple):
            return result
        return _min_return_type(*result)
    return functional.min(input)



def _wrap_multi_output(type_factory, positional_dims=1):
    """Rebuild a multi-output native call so eager results carry field names.

    Graph values stand for the whole multi-output call and are returned
    untouched, mirroring the max/min convention above.
    """
    def wrapper(input, *args, **kwargs):
        result = getattr(functional, wrapper.__name__)(input, *args, **kwargs)
        if not isinstance(result, tuple):
            return result
        return type_factory(*result)
    wrapper.__name__ = type_factory.__name__.removesuffix("_return_type")
    wrapper.__qualname__ = wrapper.__name__
    return wrapper


cummax = _wrap_multi_output(_cummax_return_type)
cummin = _wrap_multi_output(_cummin_return_type)
kthvalue = _wrap_multi_output(_kthvalue_return_type)
mode = _wrap_multi_output(_mode_return_type)
sort = _wrap_multi_output(_sort_return_type)
topk = _wrap_multi_output(_topk_return_type)
nanmedian = _wrap_multi_output(_nanmedian_return_type)


def aminmax(input, *args, **kwargs):
    result = functional.aminmax(input, *args, **kwargs)
    if not isinstance(result, tuple):
        return result
    return _aminmax_return_type(*result)


def median(input, *args, **kwargs):
    # Without a dimension the reduction collapses to a single value; the
    # dimension form returns the (values, indices) pair.
    result = functional.median(input, *args, **kwargs)
    if not isinstance(result, tuple):
        return result
    return _median_return_type(*result)


# ---------------------------------------------------------------------------
# Sparse legacy matmuls: each is a thin routing over the sparse namespace.
# ---------------------------------------------------------------------------
def spmm(mat1, mat2):
    """Matrix product of a sparse COO/CSR matrix ``mat1`` with dense ``mat2``."""
    from tensorplay import sparse as _sparse
    return _sparse.mm(mat1, mat2)


def dsmm(mat1, mat2):
    """Matrix product of a dense matrix ``mat1`` with sparse ``mat2``.

    The sparse backend has no transposed product yet, so ``mat2`` is
    materialized for this legacy face; prefer the ``@`` operator or the
    sparse namespace for large operands.
    """
    return mat1 @ mat2.to_dense()


def hsmm(mat1, mat2):
    """Hybrid matrix product: dense ``mat1`` by sparse ``mat2``, dense result."""
    return dsmm(mat1, mat2)


def saddmm(input, mat1, mat2, *, beta=1, alpha=1, out=None):
    """``beta * input + alpha * (mat1 @ mat2)`` with sparse ``mat1`` and dense
    ``mat2``; ``input`` is a sparse matrix.

    A zero ``beta`` drops ``input`` entirely, so the sparse operand never goes
    through a dense copy on that path.
    """
    from tensorplay import sparse as _sparse
    if beta == 0:
        result = _sparse.mm(mat1, mat2)
        if alpha != 1:
            result = result * alpha
    else:
        result = _sparse.addmm(input.to_dense(), mat1, mat2, beta=beta, alpha=alpha)
    if out is not None:
        out.resize_as_(result).copy_(result)
        return out
    return result


# ---------------------------------------------------------------------------
# Autocast state queries/setters that round out the public surface.
# ---------------------------------------------------------------------------
def is_autocast_cpu_enabled() -> builtins.bool:
    r"""Reports whether autocast is enabled on CPU."""
    return _C.is_autocast_enabled("cpu")


def set_autocast_cpu_enabled(enabled: builtins.bool) -> None:
    r""" Enables or disables autocast for the CPU. """
    _C.set_autocast_enabled("cpu", enabled)


def set_autocast_cpu_dtype(dtype) -> None:
    r"""Sets the autocast dtype for the CPU to ``dtype``."""
    if dtype not in (bfloat16, float16):
        raise ValueError(
            "Supported CPU autocast dtypes are bfloat16 and float16; "
            f"got {dtype}"
        )
    _C.set_autocast_dtype("cpu", dtype)


def set_autocast_gpu_dtype(dtype) -> None:
    r"""Sets the autocast dtype for CUDA to ``dtype``."""
    if dtype not in (bfloat16, float16):
        raise ValueError(
            "Supported CUDA autocast dtypes are bfloat16 and float16; "
            f"got {dtype}"
        )
    _C.set_autocast_dtype("cuda", dtype)


# ---------------------------------------------------------------------------
# Futures and device-module helpers.
# ---------------------------------------------------------------------------
def wait(future):
    """Waits on the future and returns its value when it completes."""
    return future.wait()


@functools.cache
def get_device_module(device=None):
    """Returns the module associated with a given device
    (e.g. ``tensorplay.device('cuda')``, ``"cpu"``).

    If no device is given, return the module for the current accelerator, or
    CPU if no accelerator is present.
    """
    if isinstance(device, Device):
        device_module_name = device.type
    elif isinstance(device, str):
        device_module_name = Device(device).type
    elif device is None:
        from . import accelerator as _accelerator
        _acc = _accelerator.current_accelerator()
        device_module_name = _acc.type if _acc is not None else "cpu"
    else:
        raise RuntimeError(
            f"Invalid value of device '{device}', expect tensorplay.device, str, or None"
        )
    device_module = globals().get(device_module_name, None)
    if device_module is None:
        try:
            device_module = importlib.import_module(f".{device_module_name}", __name__)
        except ModuleNotFoundError:
            device_module = None
    if device_module is None:
        raise RuntimeError(
            f"Device '{device_module_name}' does not have a corresponding module "
            f"registered as 'tensorplay.{device_module_name}'."
        )
    return device_module



[docs]
def gradient(input, *, spacing=None, dim=None, edge_order=1):
    """

    The native binding only takes ``Tensor[]`` spacing / ``int[]`` dim;
    materialize python numbers into scalar tensors typed like ``input`` (a
    single scalar applies to every dimension).
    """
    dims = [dim] if isinstance(dim, builtins.int) else (
        list(dim) if dim is not None else list(builtins.range(input.dim())))
    if spacing is None:
        sp = []
    elif isinstance(spacing, (builtins.int, builtins.float)):
        sp = [full([], spacing, dtype=input.dtype) for _ in dims]
    elif isinstance(spacing, Tensor):
        # A single coordinate tensor (numpy-style) applies to every
        # requested dimension; the kernel broadcasts numel()==n vs ==1.
        sp = [spacing]
    else:
        sp = [v if isinstance(v, Tensor) else full([], v, dtype=input.dtype)
              for v in spacing]
    return _C.gradient(self=input, spacing=sp, dim=dims, edge_order=edge_order)


################################################################################
# Import most common subpackages
################################################################################

# Use the redundant form so that type checkers know that these are a part of
# the public API. The "regular" import lines are there solely for the runtime
# side effect of adding to the imported module's members for other users.

# needs to be before import tensorplay.nn as nn to avoid circular dependencies
from tensorplay.autograd import (
    enable_grad as enable_grad,
    inference_mode as inference_mode,
    no_grad as no_grad,
    set_grad_enabled as set_grad_enabled,
    is_grad_enabled as is_grad_enabled,
)

from tensorplay import (
    __config__ as __config__,
    __future__ as __future__,
    autograd as autograd,
    backends as backends,
    cuda as cuda,
    futures as futures,
    hub as hub,
    multiprocessing as multiprocessing,
    nn as nn,
    optim as optim,
    overrides as overrides,
    types as types,
    utils as utils,
)


if TYPE_CHECKING:
    # Import the following modules during type checking to enable code intelligence features,
    # such as auto-completion for tools like pylance, even when these modules are not explicitly
    # imported in user code.
    from tensorplay import (
        export as export,
        func as func,
        quantum as quantum,
    )

else:
    _lazy_modules = {
        "accelerator",
        "audio",
        "cpu",
        "export",
        "func",
        "distributions",
        "fft",
        "linalg",
        "ao",
        "contrib",
        "masked",
        "metrics",
        "monitor",
        "nested",
        "package",
        "quantum",
        "return_types",
        "signal",
        "snn",
        "sparse",
        "special",
        "storage",
        "testing",
        "vision",
    }

    # Public single names that live inside a lazily loaded module.  The
    # structural control flow operators land here: their modules reach back
    # into the package surface through the graph and nn layers, and the
    # symbolic-shape layer they build on pulls in sympy, which is by far the
    # most expensive import in the package.  Nothing outside this file
    # touches the operators during initialization, so first use pays for
    # them.
    _lazy_attrs = {
        "quantized_lstm": ("ao.nn.quantized._ops", "quantized_lstm"),
        "quantized_gru": ("ao.nn.quantized._ops", "quantized_gru"),
        "vmap": ("func", "vmap"),
        "cond": ("_higher_order_ops", "cond"),
        "while_loop": ("_higher_order_ops", "while_loop"),
        "OutOfMemoryError": ("cuda", "OutOfMemoryError"),
    }

    def __getattr__(name):
        # Lazy modules
        if name in _lazy_modules:
            return importlib.import_module(f".{name}", __name__)
        if name in _lazy_attrs:
            module, attr = _lazy_attrs[name]
            value = getattr(importlib.import_module(f".{module}", __name__), attr)
            # Bind into the package globals so the eager operators cost a
            # dict lookup per call rather than an import machinery round trip.
            globals()[name] = value
            return value
        raise AttributeError(f"module '{__name__}' has no attribute '{name}'")


def _import_device_backends():
    """
    Leverage the Python plugin mechanism to load out-of-the-tree device extensions.
    """
    from importlib.metadata import entry_points

    group_name = "tensorplay.backends"
    if sys.version_info < (3, 10):
        backend_extensions = entry_points().get(group_name, ())
    else:
        backend_extensions = entry_points(group=group_name)

    for backend_extension in backend_extensions:
        try:
            # Load the extension
            entrypoint = backend_extension.load()
            # Call the entrypoint
            entrypoint()
        except Exception as err:
            raise RuntimeError(
                f"Failed to load the backend extension: {backend_extension.name}. "
                f"You can disable extension auto-loading with TENSORPLAY_DEVICE_BACKEND_AUTOLOAD=0."
            ) from err


def _is_device_backend_autoload_enabled() -> builtins.bool:
    """
    Whether autoloading out-of-the-tree device extensions is enabled.
    The switch depends on the value of the environment variable
    `TENSORPLAY_DEVICE_BACKEND_AUTOLOAD`.

    Returns:
        bool: Whether to enable autoloading the extensions. Enabled by default.

    Examples:
        >>> tensorplay._is_device_backend_autoload_enabled()
        True
    """
    # enabled by default
    return os.getenv("TENSORPLAY_DEVICE_BACKEND_AUTOLOAD", "1") == "1"


def _check(cond, msg=None):
    """
    Assert a condition that may be backed by a symbolic expression.

    Under eager execution a plain check: a false condition raises
    ``RuntimeError`` carrying ``msg``.  Symbolic backends may turn the check
    into a deferred assertion instead of an immediate failure.

    ``msg`` may be given as something to be called rather than as the message
    itself, so that a caller whose message costs something to write does not
    pay for it on the way to a check that holds.  A message that is a callable
    is called only once the check has failed, and what it returns is the
    message.
    """

    if not cond:
        if callable(msg):
            msg = msg()
        raise RuntimeError(msg if msg is not None else "Expected cond to be true")


def _as_tensor_fullprec(t):
    """
    Like tensorplay.as_tensor, but when given Python data types it will keep
    them in full precision.  Used when a captured region converts Python
    values to tensors and the exact scalar widths must survive.
    """
    ty = type(t)
    if ty is builtins.float:
        return tensorplay.as_tensor(t, dtype=tensorplay.float64)
    elif ty is builtins.int:
        return tensorplay.as_tensor(t, dtype=tensorplay.int64)
    else:
        return tensorplay.as_tensor(t)


# ---------------------------------------------------------------------------
# Public-surface presentation pass.  Runs last so it sees every binding the
# extension installed.
# ---------------------------------------------------------------------------

class _CleanFunction:
    """Python shim over a bound C entry whose own object is an extension
    helper type.  Only the object identity differs: ``__call__`` forwards to
    the original, and the docstring is carried over."""

    # The identity attributes live in the instance __dict__: ``__doc__`` and
    # ``__module__`` are class attributes and cannot be slots.
    __slots__ = ("_fn", "__dict__")

    def __init__(self, fn, name):
        self._fn = fn
        self.__name__ = self.__qualname__ = name
        self.__doc__ = fn.__doc__
        self.__module__ = getattr(fn, "__module__", None) or "tensorplay"
        self.__wrapped__ = fn

    def __repr__(self):
        return f"<built-in function {self.__name__}>"

    def __call__(self, *args, **kwargs):
        return self._fn(*args, **kwargs)


def _polish_public_surface():
    # The package's own ``types`` submodule shadows the standard module here.
    import types as _stdlib_types

    # 1) Classes re-exported here but registered on the extension quote the
    #    extension's module path in type errors and reprs ("tensorplay._C.DType"
    #    in `unsupported operand type(s)`); retitle them to the public package.
    for name, obj in list(globals().items()):
        if name.startswith("_") or not isinstance(obj, type):
            continue
        # Match on the exported name: a class may be registered on the
        # extension under an internal spell (``TensorBase``) while the
        # public alias differs, so its own __qualname__ is not a usable
        # lookup key.  Rewriting __qualname__ to the exported spelling
        # keeps ``tensorplay.<name>`` the one name everywhere (generated
        # code relies on that resolution).
        if (
            obj.__module__ in ("tensorplay", "tensorplay._C")
            and globals().get(name) is obj
        ):
            # immutable extension types (e.g. Size) reject retitling; the
            # exported spelling is already theirs to keep then
            try:
                if obj.__module__ != "tensorplay":
                    obj.__module__ = "tensorplay"
                if obj.__qualname__ != name:
                    obj.__qualname__ = name
            except TypeError:
                pass
    # 2) Extension functions carry a binding-helper object as their bound
    #    self; repr() and __qualname__ then quote that helper's C type name
    #    ("PyCapsule").  Wrap those in a shim with a clean repr.  Regular
    #    builtins, extension classes and non-callables pass through
    #    untouched.
    for module in list(sys.modules.values()):
        if module is None or not isinstance(module, _stdlib_types.ModuleType):
            continue
        module_name = getattr(module, "__name__", "")
        if not module_name.startswith("tensorplay") or "._" in module_name:
            continue
        for name, obj in list(vars(module).items()):
            # The shim only targets C functions, so submodules and other
            # non-callables are skipped.  Probing them is not just wasted
            # work: attribute access on lazy proxies such as the cuFFT
            # plan-cache manager runs device init and raises on CPU-only
            # builds, and getattr's default only suppresses AttributeError.
            if (
                name.startswith("_")
                or isinstance(obj, (type, _stdlib_types.ModuleType))
                or not callable(obj)
            ):
                continue
            try:
                qualname = getattr(obj, "__qualname__", None)
            except Exception:
                # a callable proxy may still run arbitrary code on access
                continue
            if not isinstance(qualname, str) or not qualname.startswith("PyCapsule."):
                continue
            vars(module)[name] = _CleanFunction(obj, name)


# `_import_device_backends` should be kept at the end to ensure
# all the other functions in this module that may be accessed by
# an autoloaded backend are defined
if _is_device_backend_autoload_enabled():
    _import_device_backends()

_polish_public_surface()


# Structural control flow operators; map and scan stay under
# tensorplay._higher_order_ops.  ``cond`` and ``while_loop`` bind through the
# package ``__getattr__`` above rather than an import here: the operator
# modules reach back into the package surface through the graph and nn layers,
# so binding them during initialization both closes that cycle and forces the
# symbolic-shape machinery (and sympy with it) into every session that never
# runs a control flow operator.
Ask DeepWiki