TensorPlay
latest (dev)
Copy
View Markdown

Latest development documentation · Updated 2026-10-15

Source code for tensorplay.compiler._core.api

"""Public compiler orchestration for TensorPlay.

Capture, backend selection, and execution concerns are separate. A backend is
never asked to discover Python control flow; it only receives a captured
``GraphModule`` and example inputs.
"""

from __future__ import annotations

import contextlib
import dis
import functools
import inspect
import re
import threading
import types
from typing import Any, Callable
from weakref import WeakSet

from tensorplay.graph import (
    GraphCaptureError,
    GraphModule,
    Tracer,
    compiler_context,
)
from tensorplay.graph._utils import _capture_disabled
from .guards import GuardChain, build_guard_chain, format_recompile_reasons
from tensorplay.graph.passes import (
    ConstFold,
    DeadCodeElimination,
    DecomposePass,
    NormalizeOperators,
    PassManager,
    PointwiseFusionHint,
    ShapeProp,
)
from tensorplay.graph.passes.dialect.common import CSEPass, get_CSE_banned_ops
from tensorplay.graph.passes.regional_compile_invoke_subgraph import (
    regional_compile_invoke_subgraph,
)
from .registry import CompilerFn, get_default_backend, lookup_backend
from .region_cache import (
    load_region,
    region_key as make_region_key,
    store_region,
)


_compiled_wrappers: WeakSet[Any] = WeakSet()
_DEFAULT_RECOMPILE_LIMIT = 8


def _compiler_context() -> Any:
    """Capture context owned by the graph namespace."""

    return compiler_context(require_native=True)


def _stride_signature(value: Any, *, dynamic: bool) -> tuple[Any, ...]:
    stride = getattr(value, "stride", ())
    strides = tuple(stride() if callable(stride) else stride)
    if not dynamic:
        return strides
    sizes = value.shape
    candidates = {}
    inferred = [None] * len(strides)
    # Express dense strides through dimensions so changing sizes can reuse
    # a specialization while changing storage order cannot.
    for stride, neg_index in sorted((s, -i) for i, s in enumerate(strides)):
        index = -neg_index
        contiguous = (
            index + 1 < len(sizes)
            and stride == sizes[index + 1] * strides[index + 1]
        )
        if stride in (0, 1) and not contiguous:
            expression = stride
        else:
            expression = candidates.get(stride, ("stride", index, stride))
        inferred[index] = expression
        candidates[sizes[index] * stride] = ("size", index, expression)
    return tuple(inferred)


def _tensor_signature(value: Any, *, dynamic: bool) -> tuple[Any, ...] | None:
    module_name = type(value).__module__
    if not module_name.startswith("tensorplay"):
        return None
    shape = getattr(value, "shape", None)
    if callable(shape):
        shape = shape()
    try:
        shape = tuple(int(item) for item in shape)
        # Dynamic mode keeps rank specialization but removes concrete sizes.
        # shape policy; operations still receive the real runtime tensors.
        shape_key = ("dynamic", len(shape)) if dynamic else shape
    except (TypeError, ValueError):
        shape_key = repr(shape)
    dtype = getattr(value, "dtype", None)
    if callable(dtype):
        dtype = dtype()
    device = getattr(value, "device", None)
    if callable(device):
        device = device()
    requires_grad = getattr(value, "requires_grad", None)
    if callable(requires_grad):
        requires_grad = requires_grad()
    return (
        "tensor",
        type(value),
        shape_key,
        repr(dtype),
        repr(device),
        bool(requires_grad),
        _stride_signature(value, dynamic=dynamic),
    )


def _value_signature(value: Any, *, dynamic: bool) -> Any:
    tensor_key = _tensor_signature(value, dynamic=dynamic)
    if tensor_key is not None:
        return tensor_key
    if value is None or isinstance(value, (bool, int, float, str, bytes)):
        return (type(value), value)
    if isinstance(value, tuple):
        return (tuple, tuple(_value_signature(item, dynamic=dynamic) for item in value))
    if isinstance(value, list):
        return (list, tuple(_value_signature(item, dynamic=dynamic) for item in value))
    if isinstance(value, dict):
        items = sorted(
            (
                (
                    _value_signature(key, dynamic=dynamic),
                    _value_signature(item, dynamic=dynamic),
                )
                for key, item in value.items()
            ),
            key=repr,
        )
        return (dict, tuple(items))
    return (type(value), id(value))


def _input_signature(
    args: tuple[Any, ...], kwargs: dict[str, Any], *, dynamic: bool
) -> Any:
    return (
        tuple(_value_signature(item, dynamic=dynamic) for item in args),
        tuple(
            sorted(
                (key, _value_signature(value, dynamic=dynamic))
                for key, value in kwargs.items()
            )
        ),
    )


_grad_enabled: Any = None


def _grad_state_component() -> tuple[Any, ...]:
    """The ambient autograd state at call time.

    A region captured with autograd enabled wires its outputs for backward;
    replaying it with the state flipped (or the reverse) would hand the
    caller an output under the wrong autograd contract, so the state is
    part of every specialization key and is read on every call.
    """
    global _grad_enabled
    if _grad_enabled is None:
        import tensorplay

        _grad_enabled = tensorplay.is_grad_enabled
    return (_grad_enabled(),)


def _quick_value_signature(value: Any, *, dynamic: bool) -> Any:
    """Build the hot-path guard key without repr-heavy metadata formatting."""

    if type(value).__module__.startswith("tensorplay"):
        shape = getattr(value, "shape", None)
        try:
            shape_key = ("dynamic", len(shape)) if dynamic else tuple(int(item) for item in shape)
        except (TypeError, ValueError):
            shape_key = repr(shape)
        dtype = getattr(value, "dtype", None)
        device = getattr(value, "device", None)
        device_type = getattr(device, "type", None)
        if device_type is None:
            device_type = repr(device)
        device_key = (
            device_type,
            getattr(device, "index", None),
        )
        requires_grad = getattr(value, "requires_grad", False)
        return (
            type(value), shape_key, dtype, device_key, bool(requires_grad),
            _stride_signature(value, dynamic=dynamic),
        )
    if value is None or isinstance(value, (bool, int, float, str, bytes)):
        return (type(value), value)
    if isinstance(value, tuple):
        return (tuple, tuple(_quick_value_signature(item, dynamic=dynamic) for item in value))
    if isinstance(value, list):
        return (list, tuple(_quick_value_signature(item, dynamic=dynamic) for item in value))
    if isinstance(value, dict):
        return (
            dict,
            tuple(
                sorted(
                    (
                        key,
                        _quick_value_signature(item, dynamic=dynamic),
                    )
                    for key, item in value.items()
                )
            ),
        )
    return (type(value), id(value))


def _quick_input_signature(
    args: tuple[Any, ...], kwargs: dict[str, Any], *, dynamic: bool
) -> Any:
    return (
        tuple(_quick_value_signature(item, dynamic=dynamic) for item in args),
        tuple(
            sorted(
                (key, _quick_value_signature(value, dynamic=dynamic))
                for key, value in kwargs.items()
            )
        ),
    )


def _arg_fingerprint(value: Any) -> Any:
    """Cheap per-call identity probe for the hot-path key memo.

    ``(id, version)`` for tensors: in-place mutation bumps ``_version`` so a
    cached key component is never reused across mutated inputs; fresh tensors
    have fresh ids.  Inference tensors carry no version counter and are
    immutable, so their identity alone keys the entry.  Other inputs without
    a version counter use tensor metadata.  Scalars compare by value.  This
    replaces per-call shape/dtype/device reads and tuple rebuilding, which
    profiling showed at ~40% of steady-state compiled-call time.
    """

    module = type(value).__module__
    if module.startswith("tensorplay"):
        try:
            version = value._version
        except RuntimeError:
            if getattr(value, "is_inference", lambda: False)():
                return ("t", id(value), None)
            version = ("metadata", _quick_value_signature(value, dynamic=False))
        return (
            "t",
            id(value),
            version,
            bool(getattr(value, "requires_grad", False)),
        )
    if value is None or isinstance(value, (bool, int, float, str, bytes)):
        return ("v", type(value).__name__, value)
    if isinstance(value, (tuple, list)):
        return (type(value), tuple(_arg_fingerprint(item) for item in value))
    if isinstance(value, dict):
        return (
            dict,
            tuple(sorted(
                ((_arg_fingerprint(key), _arg_fingerprint(item)) for key, item in value.items()),
                key=repr,
            )),
        )
    return ("o", id(value))


def _call_fingerprint(args: tuple[Any, ...], kwargs: dict[str, Any]) -> tuple:
    items = [_arg_fingerprint(item) for item in args]
    if kwargs:
        items.extend(
            (key, _arg_fingerprint(kwargs[key])) for key in sorted(kwargs)
        )
    return tuple(items)


def _backend_kwargs(
    *,
    mode: str | None,
    options: dict[str, Any] | None,
    name: str | None,
    dynamic: bool | None,
    strict_native: bool,
) -> dict[str, Any]:
    kwargs: dict[str, Any] = {}
    if mode is not None and mode != "default":
        kwargs["mode"] = mode
    if options:
        kwargs["options"] = dict(options)
    if name is not None:
        kwargs["name"] = name
    if dynamic is not None:
        kwargs["dynamic"] = dynamic
    if strict_native:
        # Capture may run in Python, but the returned callable must be a
        # native backend executable when this contract is requested.
        kwargs["strict_native"] = True
    return kwargs


def _log_recompiles(config_verbose: bool) -> bool:
    import os

    return config_verbose or os.environ.get("TP_LOG_RECOMPILES", "") not in ("", "0")



[docs]
def compile(
    model: Callable[..., Any] | None = None,
    *,
    fullgraph: bool = False,
    dynamic: bool | None = None,
    backend: str | CompilerFn | None = None,
    mode: str | None = None,
    options: dict[str, Any] | None = None,
    name: str | None = None,
    disable: bool = False,
    recompile_limit: int | None = None,
    isolate_recompiles: bool = False,
    strict_native: bool = False,
    dynamic_shapes: Any = None,
) -> Callable[..., Any]:
    """Compile a callable through the TensorPlay compiler frontend.

    ``backend`` may be a registered name or a callable with the contract
    ``backend(graph_module, example_inputs, **kwargs) -> callable``.  The
    frontend caches specializations by input metadata; capture and backend
    failures are surfaced as compiler errors.
    """

    normalized_dynamic_shapes = dynamic_shapes
    if dynamic_shapes is not None:
        if dynamic is not None:
            raise RuntimeError("dynamic and dynamic_shapes cannot both be specified")
        if not isinstance(dynamic_shapes, bool):
            raise TypeError(
                "TensorPlay dynamic_shapes currently accepts only a bool; "
                "use a bool dynamic policy for this frontend"
            )
        dynamic = dynamic_shapes

    if mode is not None and options is not None:
        raise RuntimeError("Either mode or options can be specified, but not both")
    if mode is None and options is None:
        mode = "default"
    if options is not None and not isinstance(options, dict):
        raise TypeError(f"options must be a dict, got {type(options)!r}")

    from tensorplay.compiler import config as compiler_config

    configured_dynamic = compiler_config.dynamic_shapes
    if dynamic is None and normalized_dynamic_shapes is None:
        if configured_dynamic is not None:
            dynamic = configured_dynamic
        elif not compiler_config.assume_static_by_default:
            dynamic = True
    if not isinstance(compiler_config.assume_static_by_default, bool):
        raise TypeError("config.assume_static_by_default must be a bool")
    if not isinstance(compiler_config.verbose, bool):
        raise TypeError("config.verbose must be a bool")
    if not isinstance(compiler_config.fail_on_recompile_limit_hit, bool):
        raise TypeError("config.fail_on_recompile_limit_hit must be a bool")
    if not isinstance(compiler_config.force_disable_caches, bool):
        raise TypeError("config.force_disable_caches must be a bool")
    accumulated_limit = compiler_config.accumulated_recompile_limit
    if (
        not isinstance(accumulated_limit, int)
        or isinstance(accumulated_limit, bool)
        or accumulated_limit < 1
    ):
        raise ValueError("config.accumulated_recompile_limit must be a positive integer")

    if model is None:
        return lambda actual_model: compile(
            actual_model,
            fullgraph=fullgraph,
            backend=backend,
            dynamic=dynamic,
            mode=mode,
            options=options,
            name=name,
            disable=disable,
            recompile_limit=recompile_limit,
            isolate_recompiles=isolate_recompiles,
            strict_native=strict_native,
            # ``dynamic_shapes`` has already been normalized into ``dynamic``
            # above.  Passing it again would look like the user supplied both
            # mutually exclusive knobs on the recursive decorator call.
            dynamic_shapes=None,
        )
    if not callable(model):
        raise TypeError(f"compile() expected a callable, got {type(model)!r}")
    if disable:
        return model
    if recompile_limit is not None and recompile_limit < 1:
        raise ValueError("recompile_limit must be positive")

    lifted = _lift_free_tensors(model)
    if lifted is not None:
        program, read_free = lifted
        return _WithFreeTensors(
            model,
            compile(
                program,
                fullgraph=fullgraph,
                backend=backend,
                dynamic=dynamic,
                mode=mode,
                options=options,
                name=name,
                recompile_limit=recompile_limit,
                isolate_recompiles=isolate_recompiles,
                strict_native=strict_native,
            ),
            read_free,
        )

    backend_spec = get_default_backend() if backend is None else backend
    compiler_fn = lookup_backend(backend_spec)
    backend_kwargs = _backend_kwargs(
        mode=mode,
        options=options,
        name=name,
        dynamic=dynamic,
        strict_native=strict_native,
    )
    specialization_dynamic = dynamic is True
    specialization_limit = (
        _DEFAULT_RECOMPILE_LIMIT if recompile_limit is None else recompile_limit
    )
    if recompile_limit is None:
        specialization_limit = compiler_config.recompile_limit
    if (
        not isinstance(specialization_limit, int)
        or isinstance(specialization_limit, bool)
        or specialization_limit < 1
    ):
        raise ValueError("recompile_limit must be a positive integer")
    cache_enabled = not compiler_config.force_disable_caches
    compile_attempts = 0
    cache: dict[Any, Callable[..., Any]] = {}
    backend_variants: dict[Any, list[Callable[..., Any]]] = {}
    guard_chains: dict[Any, GuardChain] = {}
    lock = threading.RLock()
    last_quick_key: Any = object()
    last_compiled_fn: Callable[..., Any] | None = None
    guard_param_names: tuple[str, ...] = ()
    gate_evaluator: Callable[..., tuple] | None = None
    target_cache = model.forward if _is_module_like(model) else model
    try:
        target_signature: Any = inspect.signature(target_cache)
    except (TypeError, ValueError):
        target_signature = None
    last_call_fp: Any = None
    last_quick_parts: tuple[Any, ...] | None = None

    def _guard_component(
        args_: tuple[Any, ...],
        kwargs_: dict[str, Any],
        builder: Callable[..., Any],
    ) -> tuple[Any, ...]:
        if not guard_param_names:
            return ()
        if target_signature is None:
            return ("shape-guards", "unbound")
        try:
            bound = target_signature.bind_partial(*args_, **kwargs_)
            bound.apply_defaults()
        except (TypeError, ValueError):
            return ("shape-guards", "unbound")
        return (
            "shape-guards",
            tuple(
                builder(bound.arguments.get(name), dynamic=False)
                for name in guard_param_names
            ),
        )

    def _bind_dispatcher_fast(fast: Any, nargs: int) -> None:
        """Hand a resolved steady-state entry to the outer C dispatcher.

        The dispatcher re-enters it directly once its argument memo passes;
        any slow call routes through here again, so the binding stays
        current across recompiles and specialization switches.

        The argument memo cannot see the ambient autograd state, so the
        entry carries the state it was resolved under and falls back to the
        trampoline when a call arrives under a flipped state; the trampoline
        then resolves (and binds) the specialization for that state.
        """

        bound_grad_state = _grad_state_component()

        def _fast(*args: Any) -> Any:
            if _grad_state_component() == bound_grad_state:
                return fast(*args)
            return optimized(*args)

        dispatcher.tpx_set_fast(_fast, nargs)

    @functools.wraps(model)
    def optimized(*args: Any, **kwargs: Any) -> Any:
        nonlocal last_quick_key, last_compiled_fn
        nonlocal guard_param_names, gate_evaluator
        nonlocal last_call_fp, last_quick_parts
        nonlocal compile_attempts
        if _capture_disabled.get():
            return model(*args, **kwargs)
        # Steady-state memo: identical objects with unchanged versions (or
        # unchanged scalars) cannot produce different signatures or gate
        # outcomes -- skip metadata reads and evaluator replay entirely.
        # Tensor-positional calls ask the C dispatcher for a read-only
        # certificate against the trampoline fingerprint (one probe loop in
        # C replaces the Python (id, version) walk); every other call shape
        # falls back to the Python fingerprint below.
        if dispatcher is not None:
            matched = not kwargs and dispatcher.tpx_fingerprint_matches(*args)
            if not matched:
                # Refresh the certificate for this call up front so it stays
                # paired with the quick memo built below even when resolution
                # raises.  Calls the fingerprint cannot describe (scalars,
                # kwargs) clear it instead of leaving a stale certificate.
                if not kwargs:
                    dispatcher.tpx_fingerprint_store(*args)
                else:
                    dispatcher.tpx_fingerprint_clear()
        else:
            matched = False
        if last_quick_parts is not None and matched:
            input_signature, shape_component, data_component = last_quick_parts
        else:
            call_fp = _call_fingerprint(args, kwargs)
            if last_quick_parts is not None and call_fp == last_call_fp:
                input_signature, shape_component, data_component = last_quick_parts
            else:
                input_signature = _quick_input_signature(
                    args, kwargs, dynamic=specialization_dynamic
                )
                shape_component = _guard_component(
                    args, kwargs, _quick_value_signature
                )
                data_component = gate_evaluator(args, kwargs) if gate_evaluator else ()
            last_quick_parts = (
                input_signature,
                shape_component,
                data_component,
            )
            last_call_fp = call_fp
        grad_state = _grad_state_component()
        quick_key = (input_signature, shape_component, data_component, grad_state)
        with lock:
            if cache_enabled and cache and last_compiled_fn is not None and quick_key == last_quick_key:
                compiled_fn = last_compiled_fn
            else:
                key = (
                    _input_signature(args, kwargs, dynamic=specialization_dynamic),
                    _guard_component(args, kwargs, _value_signature),
                    data_component,
                    grad_state,
                )
                compiled_fn = cache.get(key) if cache_enabled else None
            if compiled_fn is not None:
                check = getattr(compiled_fn, "_tensorplay_guard", None)
                if check is not None and not check(*args, **kwargs):
                    key = (
                        _input_signature(args, kwargs, dynamic=specialization_dynamic),
                        _guard_component(args, kwargs, _value_signature),
                        data_component,
                        grad_state,
                    )
                    compiled_fn = next((
                        variant for variant in backend_variants.get(key, ())
                        if variant is not compiled_fn and getattr(variant, "_tensorplay_guard")(*args, **kwargs)
                    ), None)
            if not cache:
                backend_variants.clear()
            store_compiled = cache_enabled
            if compiled_fn is None:
                variant_count = len(cache) + sum(
                    max(0, len(variants) - 1) for variants in backend_variants.values()
                )
                if variant_count >= specialization_limit:
                    if fullgraph or compiler_config.fail_on_recompile_limit_hit:
                        raise RuntimeError(
                            "TensorPlay compile specialization limit reached"
                        )
                    store_compiled = False
                # Explain the miss against every stored specialization before
                # recompiling, retaining each guard mismatch.
                reasons: list[Any] = []
                for chain in guard_chains.values():
                    reasons.extend(chain.explain(args, kwargs))
                if reasons:
                    optimized._tensorplay_last_recompile_reasons = tuple(reasons)
                    if _log_recompiles(compiler_config.verbose):
                        import warnings

                        warnings.warn(
                            "recompiling "
                            f"{getattr(model, '__name__', model)!r}: "
                            + format_recompile_reasons(reasons),
                            stacklevel=2,
                        )
                if compile_attempts >= accumulated_limit:
                    raise RuntimeError(
                        "TensorPlay accumulated recompilation limit reached"
                    )
                compile_attempts += 1
                compiled_fn, captured_gm = _compile_region(
                    model,
                    compiler_fn,
                    args,
                    kwargs,
                    fullgraph=fullgraph,
                    backend_kwargs=backend_kwargs,
                    region_key=make_region_key(
                        target_cache,
                        model if _is_module_like(model) else None,
                        key,
                    ),
                )
                # Keys gain a guard component once capture reveals metadata
                # reads or control-flow gates; invalidate so entries are stored
                # uniformly.
                promoted = _extract_shape_guard_params(captured_gm)
                replay = captured_gm.meta.get("guard_replay")
                if promoted - set(guard_param_names) or (
                    replay is not None and gate_evaluator is None
                ):
                    guard_param_names = tuple(sorted({*guard_param_names, *promoted}))
                    if replay is not None:
                        gate_target = model.forward if _is_module_like(model) else model
                        gate_evaluator = _make_gate_evaluator(replay, gate_target)
                    if cache_enabled:
                        cache.clear()
                        guard_chains.clear()
                    last_compiled_fn = None
                    last_call_fp = None
                    last_quick_parts = None
                    if gate_evaluator is not None:
                        data_component = gate_evaluator(args, kwargs)
                key = (
                    _input_signature(args, kwargs, dynamic=specialization_dynamic),
                    _guard_component(args, kwargs, _value_signature),
                    data_component,
                    grad_state,
                )
                if store_compiled:
                    cache[key] = compiled_fn
                    if getattr(compiled_fn, "_tensorplay_guard", None) is not None:
                        backend_variants.setdefault(key, []).append(compiled_fn)
                    guard_chains[key] = build_guard_chain(
                        key,
                        args=args,
                        kwargs=kwargs,
                        dynamic=specialization_dynamic,
                        target=model.forward if _is_module_like(model) else model,
                        gate_evaluator=gate_evaluator,
                    )
            last_quick_key = quick_key
            last_compiled_fn = compiled_fn
        if not kwargs:
            fast = getattr(compiled_fn, "_fast_call", None)
            if fast is not None:
                _bind_dispatcher_fast(fast, len(args))
                return compiled_fn(*args)
        return compiled_fn(*args, **kwargs)

    from tensorplay._C import _stax as _stax_native

    dispatcher: Any = _stax_native.make_call_dispatcher(optimized)

    optimized._tensorplay_backend = backend_spec  # type: ignore[attr-defined]
    optimized._tensorplay_cache = cache  # type: ignore[attr-defined]
    optimized._tensorplay_guard_chains = guard_chains  # type: ignore[attr-defined]
    optimized._tensorplay_last_recompile_reasons = ()  # type: ignore[attr-defined]
    optimized._tensorplay_original = model  # type: ignore[attr-defined]
    optimized._tensorplay_dynamic = dynamic  # type: ignore[attr-defined]
    optimized._tensorplay_dynamic_shapes = normalized_dynamic_shapes  # type: ignore[attr-defined]
    optimized._tensorplay_isolate_recompiles = isolate_recompiles  # type: ignore[attr-defined]
    optimized._tensorplay_recompile_limit = specialization_limit  # type: ignore[attr-defined]
    _compiled_wrappers.add(optimized)
    if dispatcher is not None:
        dispatcher._tensorplay_dispatcher = optimized  # type: ignore[attr-defined]
        return dispatcher
    return optimized



def _loaded_global_names(function: types.FunctionType) -> set[str]:
    """The names ``function``'s code, and code defined inside it, loads as globals."""

    loaded: set[str] = set()
    pending = [function.__code__]
    while pending:
        code = pending.pop()
        for instruction in dis.get_instructions(code):
            if instruction.opname in ("LOAD_GLOBAL", "LOAD_NAME"):
                loaded.add(instruction.argval)
        pending.extend(c for c in code.co_consts if isinstance(c, types.CodeType))
    return loaded


def _modules_reached(program: Any) -> list[Any]:
    """The modules whose state running ``program`` can update.

    The program itself or the module a bound method belongs to; for a plain
    function, the modules it reaches through its closure or the globals it
    loads -- a lambda calling a model it closes over updates that model.
    """

    import tensorplay

    module_type = tensorplay.nn.Module
    if isinstance(program, module_type):
        return [program]
    owner = getattr(program, "__self__", None)
    if isinstance(owner, module_type):
        return [owner]
    function = inspect.unwrap(program) if callable(program) else None
    if not isinstance(function, types.FunctionType):
        return []
    found: list[Any] = []

    def add(value: Any) -> None:
        if isinstance(value, module_type) and all(value is not m for m in found):
            found.append(value)

    for cell in function.__closure__ or ():
        try:
            add(cell.cell_contents)
        except ValueError:
            continue
    scope = function.__globals__
    for name in _loaded_global_names(function):
        add(scope.get(name))
    return found


def _free_tensor_sources(
    function: types.FunctionType,
) -> tuple[tuple[str, ...], tuple[int, ...]]:
    """The global names and closure cells through which ``function`` reads tensors.

    Only names the code loads as globals count -- an attribute that happens
    to share a global's name is not a read of it -- and functions defined
    inside the body are searched too, since they read the same globals.
    """

    import tensorplay

    scope = function.__globals__
    global_names = tuple(
        sorted(
            name
            for name in _loaded_global_names(function)
            if isinstance(scope.get(name), tensorplay.Tensor)
        )
    )
    cell_indices = []
    for index, cell in enumerate(function.__closure__ or ()):
        try:
            value = cell.cell_contents
        except ValueError:
            continue
        if isinstance(value, tensorplay.Tensor):
            cell_indices.append(index)
    return global_names, tuple(cell_indices)


def _module_state_slots(modules: list[Any]) -> list[tuple[Any, str, str, str]]:
    """Every parameter and buffer of ``modules``, as (module, table, name, label).

    A tensor held under several names is listed once, under the first.
    """

    slots: list[tuple[Any, str, str, str]] = []
    seen: set[int] = set()
    for index, root in enumerate(modules):
        for qualname, module in root.named_modules(remove_duplicate=True):
            for table in ("_parameters", "_buffers"):
                for name, value in getattr(module, table, {}).items():
                    if value is None or id(value) in seen:
                        continue
                    seen.add(id(value))
                    label = ".".join(p for p in (str(index), qualname, name) if p)
                    slots.append((module, table, name, label))
    return slots


def _lift_free_tensors(
    model: Callable[..., Any],
) -> tuple[Callable[..., Any], Callable[[], tuple[Any, ...]]] | None:
    """Make the tensors a function reads from outside into inputs of the region.

    A tensor a function reaches through a global or a closure -- directly, or
    as a parameter or buffer of a module it reaches that way -- is read when
    the function runs, not when it was compiled: an in-place update between
    calls, or a name rebound to another tensor, must reach the next call, and
    work done on such a tensor alone belongs to the call rather than to the
    capture.  So the region is captured from a copy of the function that takes
    those tensors as leading positional inputs, and every call reads them from
    where the function would.  Returns ``None`` when the function reads none.
    """

    if not isinstance(model, types.FunctionType) or getattr(model, "_tensorplay_lifted", False):
        return None
    try:
        signature = inspect.signature(model)
    except (TypeError, ValueError):
        return None
    global_names, cell_indices = _free_tensor_sources(model)
    slots = _module_state_slots(_modules_reached(model))
    if not global_names and not cell_indices and not slots:
        return None

    taken = set(signature.parameters)
    free_names = []
    for name in [
        *global_names,
        *(model.__code__.co_freevars[i] for i in cell_indices),
        *(label for *_, label in slots),
    ]:
        candidate = "free_" + re.sub(r"\W", "_", name)
        while candidate in taken:
            candidate = f"_{candidate}"
        taken.add(candidate)
        free_names.append(candidate)
    num_globals = len(global_names)
    num_direct = num_globals + len(cell_indices)
    count = len(free_names)
    missing = object()

    def program(*args: Any, **kwargs: Any) -> Any:
        free, rest = args[:count], args[count:]
        scope = dict(model.__globals__)
        scope.update(zip(global_names, free[:num_globals]))
        closure = model.__closure__
        if cell_indices:
            cells = list(closure)
            for index, value in zip(cell_indices, free[num_globals:num_direct]):
                cells[index] = types.CellType(value)
            closure = tuple(cells)
        body = types.FunctionType(
            model.__code__, scope, model.__name__, model.__defaults__, closure
        )
        body.__kwdefaults__ = model.__kwdefaults__
        # A module's state is read through the module, so the values handed
        # in stand in its attributes while the body runs, as a traced module's
        # own state does.
        patched = []
        try:
            for (module, _, name, _), value in zip(slots, free[num_direct:]):
                patched.append((module, name, module.__dict__.get(name, missing)))
                module.__dict__[name] = value
            return body(*rest, **kwargs)
        finally:
            for module, name, previous in reversed(patched):
                if previous is missing:
                    module.__dict__.pop(name, None)
                else:
                    module.__dict__[name] = previous

    functools.update_wrapper(program, model)
    program._tensorplay_lifted = True  # type: ignore[attr-defined]
    program.__signature__ = signature.replace(  # type: ignore[attr-defined]
        parameters=[
            inspect.Parameter(name, inspect.Parameter.POSITIONAL_ONLY)
            for name in free_names
        ]
        + list(signature.parameters.values())
    )

    def read_free() -> tuple[Any, ...]:
        scope = model.__globals__
        closure = model.__closure__
        return (
            *(scope.get(name) for name in global_names),
            *(closure[index].cell_contents for index in cell_indices),
            *(getattr(module, table).get(name) for module, table, name, _ in slots),
        )

    return program, read_free


class _WithFreeTensors:
    """A compiled region handed the tensors its function reads from outside.

    Calls go to the region compiled from the lifted copy, with the current
    value of every such tensor ahead of the caller's arguments; what the
    compiled callable reports about itself is read from that region.
    """

    def __init__(
        self,
        model: Callable[..., Any],
        compiled: Callable[..., Any],
        read_free: Callable[[], tuple[Any, ...]],
    ) -> None:
        self._compiled = compiled
        self._read_free = read_free
        self._tensorplay_original = model
        functools.update_wrapper(self, model)

    def __call__(self, *args: Any, **kwargs: Any) -> Any:
        return self._compiled(*self._read_free(), *args, **kwargs)

    def __getattr__(self, name: str) -> Any:
        if name.startswith("_tensorplay_"):
            return getattr(self._compiled, name)
        raise AttributeError(name)


def _bind_sample_arguments(
    model: Callable[..., Any],
    args: tuple[Any, ...],
    kwargs: dict[str, Any],
) -> dict[str, Any] | None:
    """Bind example call arguments to parameter names for the tracer.

    Metadata reads on placeholders (``x.shape[0] > 2``, ``range(x.ndim)``)
    then specialize statically during capture; the compile signature already
    keys on these fields, so no additional recompile conditions appear.
    """

    target = model.forward if _is_module_like(model) else model
    try:
        signature = inspect.signature(target)
        bound = signature.bind_partial(*args, **kwargs)
        bound.apply_defaults()
    except (TypeError, ValueError):
        return None
    return dict(bound.arguments)


def _is_module_like(value: Any) -> bool:
    return hasattr(value, "named_modules") and callable(
        getattr(value, "forward", None)
    )


def _region_is_training(
    example_inputs: tuple[Any, ...],
    example_kwargs: dict[str, Any],
    graph_module: GraphModule | None = None,
) -> bool:
    """Whether any example input carries an autograd requirement.

    A region needs the autograd pipeline when any example input requires
    grad, when the captured module root is in training mode, or when any
    captured attribute (module parameter or buffer) requires grad.
    """

    import tensorplay

    def touches_autograd(value: Any) -> bool:
        if isinstance(value, tensorplay.Tensor):
            return bool(value.requires_grad)
        if isinstance(value, (tuple, list)):
            return any(touches_autograd(item) for item in value)
        if isinstance(value, dict):
            return any(touches_autograd(item) for item in value.values())
        return False

    if any(touches_autograd(item) for item in example_inputs) or any(
        touches_autograd(item) for item in example_kwargs.values()
    ):
        return True
    if graph_module is None:
        return False
    # A module left in training mode is a training region even when a call's
    # inputs carry no gradient requirement: its parameters still do, and the
    # backward must be compiled alongside the forward.
    root = getattr(graph_module, "root", None)
    if isinstance(root, tensorplay.nn.Module) and root.training:
        return True
    for node in graph_module.graph.nodes:
        if node.op != "get_attr":
            continue
        try:
            value = graph_module._get_attr(node.target)
        except (AttributeError, KeyError, IndexError, TypeError):
            continue
        if touches_autograd(value):
            return True
    return False


def _adapt_backend_to_region(
    compiler_fn: CompilerFn,
    example_inputs: tuple[Any, ...],
    example_kwargs: dict[str, Any],
    graph_module: GraphModule | None = None,
    backend_kwargs: dict[str, Any] | None = None,
) -> CompilerFn:
    """Match a backend to the region being compiled.

    Training regions are routed through ahead-of-time autograd so parameters
    are lifted into explicit inputs before the backend sees them. Inference
    regions normally receive an operator trace when the backend requests it.
    A backend supporting symbolic capture can use the canonical graph directly
    to preserve shape expressions; container inputs still require flattening.
    """

    symbolic_inference = (
        getattr(graph_module, "meta", {}).get("symbolic_shapes")
        and getattr(compiler_fn, "supports_symbolic_shapes", False)
        and not graph_module.meta.get("static_shape_params")
    )
    if not _region_is_training(
        example_inputs, example_kwargs, graph_module
    ) and (not getattr(compiler_fn, "lowers_operator_graphs", False) or symbolic_inference):
        return compiler_fn
    from .aot_autograd import min_cut_rematerialization_partition
    from .common import aot_autograd

    forward_compiler = compiler_fn
    if backend_kwargs:
        kwargs = dict(backend_kwargs)

        def forward_compiler(graph, inputs):
            return compiler_fn(graph, inputs, **kwargs)

        forward_compiler.__name__ = getattr(compiler_fn, "__name__", "compiler_fn")

    from tensorplay._decomp import get_decompositions
    import tensorplay
    from .exact_narrowing import exact_narrowing_rules

    _NORM_DECOMPOSITION_OPS = (
        tensorplay.ops.tp.native_group_norm,
        tensorplay.ops.tp.native_group_norm_backward,
    )

    # A backend that lowers operator graphs says which operations it would
    # rather see as their parts; the trace writes those in their parts.
    backend_decompositions = getattr(compiler_fn, "trace_decompositions", None)
    adapted = aot_autograd(
        dynamic_shapes=(backend_kwargs or {}).get("dynamic") is True,
        fw_compiler=forward_compiler,
        bw_compiler=forward_compiler,
        # Keep the cheapest cut of forward values and let the backward
        # compute the rest again inside its own loops, rather than have the
        # forward write every value the backward reads.
        partition_fn=min_cut_rematerialization_partition,
        decompositions={
            **(backend_decompositions() if backend_decompositions is not None else {}),
            **get_decompositions(_NORM_DECOMPOSITION_OPS),
            **exact_narrowing_rules(),
        },
    )
    # The backend options are already bound into the compilers above; the
    # same options handed to the call again are not extra ones.
    adapted.options_bound = True
    return adapted


@contextlib.contextmanager
def _preserve_module_state(program: Any) -> Any:
    """Undo the state updates an executing capture makes.

    Capture runs the program to record it; in-place updates it performs on
    the parameters and buffers of the modules it reaches (running statistics,
    counters) must not count as a call, so their values are restored
    afterwards.
    """

    import tensorplay

    modules = _modules_reached(program)
    if not modules:
        yield
        return
    seen: set[int] = set()
    saved = []
    with tensorplay.no_grad():
        for module in modules:
            for t in (*module.parameters(), *module.buffers()):
                if id(t) not in seen:
                    seen.add(id(t))
                    saved.append((t, t.clone()))
    try:
        yield
    finally:
        with tensorplay.no_grad():
            for tensor, value in saved:
                if tuple(tensor.shape) == tuple(value.shape):
                    tensor.copy_(value)


@contextlib.contextmanager
def _preserve_input_values(*values: Any) -> Any:
    """Undo the input mutations an executing capture makes.

    Capture, propagation and lowering all execute the captured program to
    record or measure it; in-place updates it performs on its input tensors
    must not count as a call, so the input values are restored afterwards.
    The same values stand in for every compile-time run, which is what lets a
    program that mutates one of its arguments behave identically at compile
    time and at runtime without rewriting the caller's tensor.
    """

    import tensorplay

    with tensorplay.no_grad():
        saved: list[tuple[Any, Any]] = []

        def _save(value: Any) -> None:
            if isinstance(value, tensorplay.Tensor):
                saved.append((value, value.clone()))
            elif isinstance(value, (tuple, list)):
                for item in value:
                    _save(item)
            elif isinstance(value, dict):
                for item in value.values():
                    _save(item)

        _save(values)
    try:
        yield
    finally:
        with tensorplay.no_grad():
            for tensor, value in saved:
                if tuple(tensor.shape) == tuple(value.shape):
                    tensor.copy_(value)


def _compile_region(
    model: Callable[..., Any],
    compiler_fn: CompilerFn,
    example_inputs: tuple[Any, ...],
    example_kwargs: dict[str, Any],
    *,
    fullgraph: bool,
    backend_kwargs: dict[str, Any],
    region_key: str | None = None,
) -> tuple[Callable[..., Any], GraphModule]:
    devices = _generator_devices(example_inputs, example_kwargs)
    stored = load_region(region_key, model)
    if stored is not None:
        graph_module = stored
    else:
        import tensorplay

        with _preserve_input_values(example_inputs, example_kwargs), \
                tensorplay.random.fork_rng(devices=devices):
            try:
                with _compiler_context(), _preserve_module_state(model):
                    samples = _bind_sample_arguments(model, example_inputs, example_kwargs)
                    if (backend_kwargs or {}).get("dynamic") is True and getattr(
                        compiler_fn, "supports_symbolic_shapes", False
                    ):
                        from tensorplay.graph.experimental._symbolic_trace import SymbolicShapeTracer

                        tracer = SymbolicShapeTracer(samples or {})
                    else:
                        tracer = Tracer(execute=True)
                    # An operator that stands for a region of the program -- rather
                    # than for one operation -- has to be told that a capture is
                    # running, or it runs itself and the region it stands for is
                    # inlined into the graph as the operations that implement it.
                    # That inlined form is correct, and it is exactly what a backend
                    # holding a schedule for that region can never be handed.  The
                    # state is entered around the trace alone: the passes that
                    # follow rewrite the graph that came out, and one of them
                    # evaluates values, which must not be recorded as part of the
                    # region.
                    with tracer.proxy_mode:
                        graph_module = tracer.trace(
                            model,
                            sample_inputs=samples,
                        )
                    # Default capture pipeline: canonicalize operators, then constant
                    # folding, then decomposition, common-subexpression elimination
                    # and dead code elimination; fusion hints are stamped last so
                    # they see the final graph.  CSE runs after decomposition so
                    # shared sub-chains across rewritten composites collapse too
                    # (two gelu sites share one erf chain).  Backends always receive
                    # a folded, linted, hint-annotated graph; ShapeProp below
                    # additionally annotates tensor shapes.
                    pass_result = PassManager(
                        [
                            NormalizeOperators(),
                            ConstFold(),
                            DecomposePass(),
                            CSEPass(get_CSE_banned_ops()),
                            DeadCodeElimination(),
                            PointwiseFusionHint(),
                        ]
                    )(graph_module)
                    graph_module = pass_result.graph_module
            except GraphCaptureError as exc:
                raise GraphCaptureError(
                    "TensorPlay could not capture the requested compiler region"
                ) from exc
            store_region(region_key, graph_module)
            tracer._node_samples.clear()
            tracer.sample_inputs.clear()
            tracer._samples.clear()
            _release_recorded_values(graph_module)

    # Capture, propagation and lowering all execute the program to record or
    # measure it.  The generator they advance belongs to the caller, so the
    # whole region is compiled inside a forked RNG: the draws the graph reads
    # at runtime start from where the caller left the generator, not from
    # where the compiler's own sample pass stopped.
    import tensorplay

    with _preserve_input_values(example_inputs, example_kwargs):
        with tensorplay.random.fork_rng(devices=devices):
            # Backend failures are compiler failures, not graph breaks.  In
            # particular, a Stax lowering error must not silently turn a requested
            # compiled region into an uncompiled call.
            # Registered backends receive graph inputs in placeholder order, including
            # values supplied through keywords and defaults.  Passing only
            # positional arguments makes a keyword-only/scalar placeholder appear
            # to be missing and is especially harmful for native Stax lowering.
            bound = graph_module.signature.bind_partial(*example_inputs, **example_kwargs)
            bound.apply_defaults()
            # Numeric-gate placeholders ride the contract as synthetic inputs; their
            # trace-time values stand in at lowering so kernels see real 0-d tensors.
            backend_inputs = []
            for node in graph_module.graph.placeholders:
                parameter_name = node.target if isinstance(node.target, str) else node.name
                try:
                    backend_inputs.append(bound.arguments[parameter_name])
                except KeyError:
                    if node.name not in bound.arguments:
                        raise GraphCaptureError(
                            f"missing sample value for graph placeholder {node.name!r}"
                        ) from None
                    backend_inputs.append(bound.arguments[node.name])

            # Advisory shape/value metadata for backends and visualization; never a
            # reason to reject an otherwise compilable region.
            # Propagation executes the graph: it must not count as a call either.
            try:
                with _preserve_module_state(model), tensorplay.no_grad():
                    ShapeProp(backend_inputs)(graph_module)
            except (GraphCaptureError, RuntimeError):
                pass

            compiler_fn = _adapt_backend_to_region(
                compiler_fn,
                example_inputs,
                example_kwargs,
                graph_module,
                backend_kwargs,
            )

            regional_compile_invoke_subgraph(
                graph_module,
                compiler=compiler_fn,
                compiler_kwargs=backend_kwargs,
            )

            with _compiler_context():
                compiled = compiler_fn(graph_module, backend_inputs, **backend_kwargs)

    if not callable(compiled):
        raise TypeError(
            f"compiler backend returned {type(compiled)!r}; expected a callable"
        )
    if graph_module.meta.get("symbolic_shapes"):
        from tensorplay.compiler.backends.stax.shape_guards import build_capture_guard

        capture_guard = build_capture_guard(graph_module)
        backend_guard = getattr(compiled, "_tensorplay_guard", None)

        def check(*args, **kwargs):
            values = graph_module.signature.bind(*args, **kwargs)
            values.apply_defaults()
            ordered = [
                values.arguments[node.target] for node in graph_module.graph.placeholders
            ]
            return capture_guard(ordered) and (
                backend_guard is None or backend_guard(*args, **kwargs)
            )

        compiled._tensorplay_guard = check
    _release_recorded_values(graph_module)
    return compiled, graph_module


def _generator_devices(
    example_inputs: tuple[Any, ...], example_kwargs: dict[str, Any]
) -> list[int]:
    """The CUDA devices whose generators compiling a region may advance.

    Every device an input lives on, and the current device whenever CUDA is
    present, since a region can draw on it without reading anything there.
    Reading a device's generator state does not open a context on it.
    """

    import tensorplay

    devices: list[int] = []

    def add(index: int) -> None:
        if index not in devices:
            devices.append(index)

    for value in (*example_inputs, *example_kwargs.values()):
        if isinstance(value, tensorplay.Tensor) and value.device.type == "cuda":
            add(value.device.index or 0)
    cuda = getattr(tensorplay, "cuda", None)
    if cuda is not None and cuda.is_available():
        add(cuda.current_device())
    return devices


class _RecordedTensorMetadata:
    """Small tensor description retained for diagnostics after compilation."""

    __slots__ = (
        "shape", "dtype", "device", "requires_grad", "_stride", "_defined",
        "_storage_id", "_storage_offset",
    )

    def __init__(self, value: Any) -> None:
        shape = getattr(value, "shape", ())
        self.shape = tuple(shape() if callable(shape) else shape)
        self.dtype = getattr(value, "dtype", None)
        self.device = getattr(value, "device", None)
        self.requires_grad = bool(getattr(value, "requires_grad", False))
        stride = getattr(value, "stride", ())
        self._stride = tuple(stride() if callable(stride) else stride)
        defined = getattr(value, "defined", None)
        self._defined = not callable(defined) or bool(defined())
        try:
            self._storage_id = value.untyped_storage().data_ptr()
            self._storage_offset = value.storage_offset()
        except (AttributeError, RuntimeError):
            self._storage_id = None
            self._storage_offset = 0

    def stride(self, dim: int | None = None) -> Any:
        return self._stride if dim is None else self._stride[dim]

    def defined(self) -> bool:
        return self._defined

    def storage_offset(self) -> int:
        return self._storage_offset

    def size(self, dim: int | None = None) -> Any:
        return self.shape if dim is None else self.shape[dim]

    def dim(self) -> int:
        return len(self.shape)

    def numel(self) -> int:
        result = 1
        for size in self.shape:
            result *= int(size)
        return result


def _release_recorded_values(graph_module: Any) -> None:
    """Drop execution artifacts that capture-time propagation parked on nodes.

    Shape propagation executes the region on real tensors and leaves every
    intermediate on its node for the lowering pipeline to inspect.  Once the
    backend has produced its artifact those tensors are dead weight: one
    region can hold the entire forward's activation set, and every cached
    specialization would keep its own copy alive.  The cheap metadata each
    node carries (shape, dtype, device) stays behind, and propagation on a
    later compile of the same region regenerates whatever values the
    pipeline consumes.
    """
    from tensorplay import Tensor

    try:
        nodes = graph_module.graph.nodes
    except AttributeError:
        return

    def strip(value: Any) -> Any:
        if isinstance(value, Tensor):
            return _RecordedTensorMetadata(value)
        if isinstance(value, tuple):
            return tuple(strip(item) for item in value)
        if isinstance(value, list):
            return [strip(item) for item in value]
        if isinstance(value, dict):
            return {key: strip(item) for key, item in value.items()}
        return value

    for node in nodes:
        meta = getattr(node, "meta", None)
        if not meta:
            continue
        meta.pop("widened_source", None)
        value = meta.get("val")
        if value is not None:
            meta["val"] = strip(value)
    samples = getattr(graph_module, "meta", {}).get("sample_inputs")
    if samples is not None:
        graph_module.meta["sample_inputs"] = strip(samples)


_SHAPE_GUARD_ATTRS = frozenset({"shape", "len", "ndim"})


def _make_gate_evaluator(replay: dict[str, Any], target: Any) -> Callable[..., tuple]:
    """Build the per-call gate re-evaluator for one specialization (L1-D1).

    Replays the extracted condition subgraph on live inputs and returns the
    branch-deciding outcomes (``bool``/``iter`` gates) as the cache-key tail.
    Numeric-gate values never fragment the cache: they stay live inside the
    captured graph itself (GateValue proxies keep the condition subgraph
    reachable from the output), so the artifact recomputes them per call.
    """

    from tensorplay.graph import GraphModule, gate_outcome

    # The condition reads state off the module a bound method belongs to --
    # a buffer counting batches, say -- so the module is what it is read from;
    # the method only says how the call's arguments are named.
    owner = getattr(target, "__self__", None)
    import tensorplay

    root = owner if isinstance(owner, tensorplay.nn.Module) else target
    mini = GraphModule(
        root,
        replay["graph"],
        inspect.Signature(
            [
                inspect.Parameter(name, inspect.Parameter.POSITIONAL_OR_KEYWORD)
                for name in replay["placeholders"]
            ]
        ),
    )
    gates = replay["gates"]
    target_signature = None
    try:
        target_signature = inspect.signature(target)
    except (TypeError, ValueError):
        pass

    def evaluate(args_: tuple[Any, ...], kwargs_: dict[str, Any]) -> tuple:
        if target_signature is not None:
            try:
                bound = target_signature.bind_partial(*args_, **kwargs_)
                bound.apply_defaults()
            except (TypeError, ValueError):
                return ("gates", "unbound")
            feeds_src = {
                name: bound.arguments.get(name) for name in replay["placeholders"]
            }
        else:
            feeds_src = dict(zip(replay["placeholders"], args_))
        values = mini._interpret(**feeds_src)
        outputs = values if isinstance(values, tuple) else (values,)
        # graph.gate() nodes stay symbolic inside the captured graph, so
        # their concrete values never fragment reuse; plain int()/float()
        # consumption bakes constants into the artifact and MUST key.
        symbolic = set(replay.get("symbolic") or ())
        key_tail: list[Any] = ["gates"]
        for output, (node_name, kind) in zip(outputs, gates):
            if kind in ("int", "float", "index") and node_name in symbolic:
                continue
            key_tail.append(gate_outcome(kind, output))
        return tuple(key_tail)

    return evaluate


def _extract_shape_guard_params(graph_module: GraphModule) -> frozenset[str]:
    """Parameters whose captured metadata reads require exact-shape guards.

    Branching on ``x.shape[0]`` bakes one side of the branch into the graph,
    so a dynamic-mode cache entry may only be reused while that placeholder's
    shape stays identical.  dtype/device/reads need no extra guards: they are
    already part of every specialization signature.
    """

    touches = getattr(graph_module, "meta", {}).get("metadata_touches") or ()
    if graph_module.meta.get("symbolic_shapes"):
        return frozenset(graph_module.meta.get("static_shape_params", ()))
    names = {name for name, attr in touches if attr in _SHAPE_GUARD_ATTRS}
    if not names or graph_module.signature is None:
        return frozenset()
    return frozenset(
        name for name in graph_module.signature.parameters if name in names
    )


def reset() -> None:
    """Clear all per-wrapper compiler specializations and backend state."""

    from .registry import reset_backends

    reset_backends()
    for wrapper in list(_compiled_wrappers):
        cache = getattr(wrapper, "_tensorplay_cache", None)
        if cache is not None:
            cache.clear()
        chains = getattr(wrapper, "_tensorplay_guard_chains", None)
        if chains is not None:
            chains.clear()
        try:
            wrapper._tensorplay_last_recompile_reasons = ()
        except Exception:
            pass
Ask DeepWiki