Generated from the full canonical file for this source snapshot. Line numbers match the library source.
Source SHA256: 17dbb27f75d43cf118a44d472213a21a315730e6bc5f74181acde09c1bef6227
1"""First-order tape ownership for composed deterministic operators.23A tape is an execution lifetime, not an image Jacobian. This small owner makes4output seeding, error checkpoints and discard/reset explicit. Scientific5operators still receive the underlying tape through their tape= argument.6"""78from __future__ import annotations910from collections.abc import Sequence11from dataclasses import dataclass, field12from typing import Any, Protocol1314from dpt._runtime import load_warp, prepare_context, require_no_tape15from dpt.contracts import ContractError161718class CheckedWorkspace(Protocol):19def clear_status(self) -> None: ...20def check_status(self) -> None: ...212223# region book:autodiff-first-order-lifetime24@dataclass(slots=True)25class FirstOrderPass:26"""Own one forward/reverse evaluation and its explicit diagnostic checkpoint.2728Use as a context manager; pass .tape into canonical forward operators.29Call backward after exiting the context. Keep original inputs immutable30until close(). A new iteration requires a fresh context entry after close.31"""3233workspaces: Sequence[CheckedWorkspace]34tape: Any = field(default=None, init=False, repr=False)35_state: str = field(default="new", init=False)3637def __enter__(self) -> FirstOrderPass:38if self._state != "new":39raise ContractError("a FirstOrderPass owns exactly one evaluation")40require_no_tape()41for workspace in self.workspaces:42workspace.clear_status()43self.tape = load_warp().Tape()44self.tape.__enter__()45self._state = "recording"46return self4748def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:49self.tape.__exit__(exc_type, exc_value, traceback)50self._state = "recorded"51if exc_type is not None:52self.close()5354def backward(self, output: Any, seed: Any) -> None:55"""Seed a caller-owned output cotangent and complete numerical checks."""56if self._state != "recorded":57raise ContractError("backward requires one completed, unconsumed forward pass")58try:59wp = load_warp()60if not isinstance(output, wp.array) or output.grad is None:61raise ContractError("output must be a Warp CUDA array with a preallocated gradient")62ctx = prepare_context(device=output.device)63ctx.array(output, "output", dtype=output.dtype, ndim=output.ndim)64ctx.array(seed, "seed", dtype=output.dtype, shape=tuple(output.shape), ndim=output.ndim)65self.tape.backward(grads={output: seed})66for workspace in self.workspaces:67workspace.check_status()68self._state = "complete"69except BaseException:70self.close()71raise7273def close(self) -> None:74"""Clear accumulated cotangents and release every abandoned recording."""75if self._state == "recording":76raise ContractError("exit the recording context before closing it")77if self.tape is not None and self._state != "closed":78self.tape.zero()79self.tape.reset()80for workspace in self.workspaces:81discard = getattr(workspace, "discard_recording", None)82if discard is not None:83discard(self.tape)84self._state = "closed"858687# endregion book:autodiff-first-order-lifetime88