eae-runtime 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- eae_runtime/__init__.py +80 -0
- eae_runtime/_version.py +24 -0
- eae_runtime/adjoint.py +128 -0
- eae_runtime/backend.py +52 -0
- eae_runtime/blocks.py +69 -0
- eae_runtime/boundary_store.py +65 -0
- eae_runtime/config.py +48 -0
- eae_runtime/contrib/__init__.py +25 -0
- eae_runtime/contrib/transformer_blocks.py +259 -0
- eae_runtime/events.py +84 -0
- eae_runtime/forward_executor.py +35 -0
- eae_runtime/logging_utils.py +41 -0
- eae_runtime/memory.py +175 -0
- eae_runtime/passes/__init__.py +18 -0
- eae_runtime/passes/base.py +31 -0
- eae_runtime/passes/clip.py +25 -0
- eae_runtime/passes/logging_pass.py +29 -0
- eae_runtime/passes/quantize.py +55 -0
- eae_runtime/passes/regularization.py +47 -0
- eae_runtime/passes/synthetic_gradient.py +67 -0
- eae_runtime/pipeline.py +58 -0
- eae_runtime/profiler.py +47 -0
- eae_runtime/py.typed +0 -0
- eae_runtime/reconstruction.py +89 -0
- eae_runtime/runtime.py +180 -0
- eae_runtime/schedulers/__init__.py +38 -0
- eae_runtime/schedulers/async_scheduler.py +65 -0
- eae_runtime/schedulers/base.py +87 -0
- eae_runtime/schedulers/distributed.py +78 -0
- eae_runtime/schedulers/pipeline_scheduler.py +67 -0
- eae_runtime/schedulers/sequential.py +27 -0
- eae_runtime-0.1.0.dist-info/METADATA +229 -0
- eae_runtime-0.1.0.dist-info/RECORD +38 -0
- eae_runtime-0.1.0.dist-info/WHEEL +5 -0
- eae_runtime-0.1.0.dist-info/licenses/LICENSE +13 -0
- eae_runtime-0.1.0.dist-info/scm_file_list.json +59 -0
- eae_runtime-0.1.0.dist-info/scm_version.json +8 -0
- eae_runtime-0.1.0.dist-info/top_level.txt +1 -0
eae_runtime/__init__.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""
|
|
2
|
+
EAE Runtime: a standalone, programmable, block-local reverse-mode execution
|
|
3
|
+
engine that sits *above* PyTorch and orchestrates many local autograd
|
|
4
|
+
computations, instead of replacing PyTorch's autodiff engine.
|
|
5
|
+
|
|
6
|
+
Quick start:
|
|
7
|
+
|
|
8
|
+
import torch.nn as nn
|
|
9
|
+
from eae_runtime import EAERuntime, RuntimeConfig
|
|
10
|
+
from eae_runtime.passes import ClipPass, GaussianNoisePass
|
|
11
|
+
|
|
12
|
+
model = nn.Sequential(
|
|
13
|
+
nn.Linear(64, 64), nn.ReLU(),
|
|
14
|
+
nn.Linear(64, 64), nn.ReLU(),
|
|
15
|
+
nn.Linear(64, 10),
|
|
16
|
+
)
|
|
17
|
+
optimizer = torch.optim.SGD(model.parameters(), lr=1e-2)
|
|
18
|
+
config = RuntimeConfig(
|
|
19
|
+
scheduler="sequential",
|
|
20
|
+
memory="pool",
|
|
21
|
+
backend="auto",
|
|
22
|
+
passes=[ClipPass(max_norm=1.0)],
|
|
23
|
+
)
|
|
24
|
+
runtime = EAERuntime(model, optimizer, config)
|
|
25
|
+
loss = runtime.train_step(x, lambda out: nn.functional.mse_loss(out, target))
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from .adjoint import AdjointState
|
|
29
|
+
from .backend import BackendManager
|
|
30
|
+
from .blocks import BlockDecomposer, EAEBlock
|
|
31
|
+
from .boundary_store import BoundaryStore
|
|
32
|
+
from .config import RuntimeConfig
|
|
33
|
+
from .events import Event, EventBus, EventType
|
|
34
|
+
from .forward_executor import ForwardExecutor
|
|
35
|
+
from .memory import MemoryManager, MemoryPolicy, NullMemoryPolicy, PoolMemoryPolicy
|
|
36
|
+
from .pipeline import AdjointPipeline, PassManager
|
|
37
|
+
from .profiler import Profiler
|
|
38
|
+
from .reconstruction import ReconstructionEngine
|
|
39
|
+
from .runtime import EAERuntime
|
|
40
|
+
from .schedulers import (
|
|
41
|
+
AsyncScheduler,
|
|
42
|
+
BaseScheduler,
|
|
43
|
+
DistributedScheduler,
|
|
44
|
+
PipelineScheduler,
|
|
45
|
+
SequentialScheduler,
|
|
46
|
+
build_scheduler,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
from ._version import version as __version__
|
|
51
|
+
except ImportError: # pragma: no cover - only hit in a non-built source tree
|
|
52
|
+
__version__ = "0.0.0+unknown"
|
|
53
|
+
|
|
54
|
+
__all__ = [
|
|
55
|
+
"AdjointState",
|
|
56
|
+
"BackendManager",
|
|
57
|
+
"BlockDecomposer",
|
|
58
|
+
"EAEBlock",
|
|
59
|
+
"BoundaryStore",
|
|
60
|
+
"RuntimeConfig",
|
|
61
|
+
"Event",
|
|
62
|
+
"EventBus",
|
|
63
|
+
"EventType",
|
|
64
|
+
"ForwardExecutor",
|
|
65
|
+
"MemoryManager",
|
|
66
|
+
"MemoryPolicy",
|
|
67
|
+
"NullMemoryPolicy",
|
|
68
|
+
"PoolMemoryPolicy",
|
|
69
|
+
"AdjointPipeline",
|
|
70
|
+
"PassManager",
|
|
71
|
+
"Profiler",
|
|
72
|
+
"ReconstructionEngine",
|
|
73
|
+
"EAERuntime",
|
|
74
|
+
"AsyncScheduler",
|
|
75
|
+
"BaseScheduler",
|
|
76
|
+
"DistributedScheduler",
|
|
77
|
+
"PipelineScheduler",
|
|
78
|
+
"SequentialScheduler",
|
|
79
|
+
"build_scheduler",
|
|
80
|
+
]
|
eae_runtime/_version.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.1.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = 'g336fa5cb8'
|
eae_runtime/adjoint.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AdjointState: the first-class runtime object that flows through the EAE runtime.
|
|
3
|
+
|
|
4
|
+
Design principle (see spec, "Explicit Adjoint States"):
|
|
5
|
+
The runtime never passes raw tensors internally. Everything flows through
|
|
6
|
+
AdjointState. Passes, schedulers and the reconstruction engine all read
|
|
7
|
+
and write AdjointState objects, never bare torch.Tensors.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import copy
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from typing import Any, Dict, List, Optional
|
|
15
|
+
|
|
16
|
+
import torch
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class AdjointState:
|
|
21
|
+
"""A gradient ("adjoint") flowing backward through the runtime, plus metadata.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
tensor: the actual gradient tensor (d Loss / d activation).
|
|
25
|
+
layer_id: index of the block boundary this adjoint sits at (L, L-1, ..., 0).
|
|
26
|
+
block: human readable name of the block that produced/consumes this adjoint.
|
|
27
|
+
dtype: logical dtype this adjoint should be materialized at.
|
|
28
|
+
device: logical device this adjoint should be materialized at.
|
|
29
|
+
metadata: free-form dict for passes to stash information.
|
|
30
|
+
history: ordered list of pass names that have touched this adjoint,
|
|
31
|
+
for debugging / provenance / profiling.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
tensor: torch.Tensor
|
|
35
|
+
layer_id: int
|
|
36
|
+
block: str = ""
|
|
37
|
+
dtype: Optional[torch.dtype] = None
|
|
38
|
+
device: Optional[torch.device] = None
|
|
39
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
40
|
+
history: List[str] = field(default_factory=list)
|
|
41
|
+
|
|
42
|
+
def __post_init__(self):
|
|
43
|
+
if self.dtype is None:
|
|
44
|
+
self.dtype = self.tensor.dtype
|
|
45
|
+
if self.device is None:
|
|
46
|
+
self.device = self.tensor.device
|
|
47
|
+
|
|
48
|
+
# ------------------------------------------------------------------ #
|
|
49
|
+
# Rich adjoint API - users should almost never touch .tensor directly
|
|
50
|
+
# ------------------------------------------------------------------ #
|
|
51
|
+
def norm(self, p: float = 2.0) -> torch.Tensor:
|
|
52
|
+
"""L-p norm of the underlying gradient tensor."""
|
|
53
|
+
return torch.linalg.vector_norm(self.tensor.float(), ord=p)
|
|
54
|
+
|
|
55
|
+
def statistics(self) -> Dict[str, float]:
|
|
56
|
+
"""Cheap summary statistics, useful for logging/profiling passes."""
|
|
57
|
+
t = self.tensor.detach().float()
|
|
58
|
+
return {
|
|
59
|
+
"mean": t.mean().item(),
|
|
60
|
+
"std": t.std(unbiased=False).item() if t.numel() > 1 else 0.0,
|
|
61
|
+
"min": t.min().item(),
|
|
62
|
+
"max": t.max().item(),
|
|
63
|
+
"norm": torch.linalg.vector_norm(t).item(),
|
|
64
|
+
"numel": t.numel(),
|
|
65
|
+
"has_nan": bool(torch.isnan(t).any().item()),
|
|
66
|
+
"has_inf": bool(torch.isinf(t).any().item()),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
def quantize(self, dtype: torch.dtype = torch.float16) -> "AdjointState":
|
|
70
|
+
"""Return a new AdjointState with the tensor cast down to `dtype`."""
|
|
71
|
+
new = self.clone()
|
|
72
|
+
new.tensor = new.tensor.to(dtype)
|
|
73
|
+
new.dtype = dtype
|
|
74
|
+
return new
|
|
75
|
+
|
|
76
|
+
def dequantize(self, dtype: torch.dtype = torch.float32) -> "AdjointState":
|
|
77
|
+
new = self.clone()
|
|
78
|
+
new.tensor = new.tensor.to(dtype)
|
|
79
|
+
new.dtype = dtype
|
|
80
|
+
return new
|
|
81
|
+
|
|
82
|
+
def compress(self, ratio: float = 0.5) -> "AdjointState":
|
|
83
|
+
"""Simple magnitude-pruning compression pass, kept out of the runtime
|
|
84
|
+
core on purpose - this is exactly the kind of thing a real Pass would
|
|
85
|
+
do; it lives here only as a convenience primitive on the adjoint."""
|
|
86
|
+
new = self.clone()
|
|
87
|
+
flat = new.tensor.flatten()
|
|
88
|
+
k = max(1, int(flat.numel() * (1.0 - ratio)))
|
|
89
|
+
if k < flat.numel():
|
|
90
|
+
threshold = flat.abs().kthvalue(flat.numel() - k + 1).values
|
|
91
|
+
mask = flat.abs() >= threshold
|
|
92
|
+
flat = flat * mask
|
|
93
|
+
new.tensor = flat.view_as(new.tensor)
|
|
94
|
+
return new
|
|
95
|
+
|
|
96
|
+
def to(self, device: Optional[torch.device] = None, dtype: Optional[torch.dtype] = None) -> "AdjointState":
|
|
97
|
+
new = self.clone()
|
|
98
|
+
new.tensor = new.tensor.to(device=device or new.device, dtype=dtype or new.dtype)
|
|
99
|
+
new.device = new.tensor.device
|
|
100
|
+
new.dtype = new.tensor.dtype
|
|
101
|
+
return new
|
|
102
|
+
|
|
103
|
+
def clone(self) -> "AdjointState":
|
|
104
|
+
return AdjointState(
|
|
105
|
+
tensor=self.tensor,
|
|
106
|
+
layer_id=self.layer_id,
|
|
107
|
+
block=self.block,
|
|
108
|
+
dtype=self.dtype,
|
|
109
|
+
device=self.device,
|
|
110
|
+
metadata=dict(self.metadata),
|
|
111
|
+
history=list(self.history),
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def detach(self) -> "AdjointState":
|
|
115
|
+
new = self.clone()
|
|
116
|
+
new.tensor = new.tensor.detach()
|
|
117
|
+
return new
|
|
118
|
+
|
|
119
|
+
def record(self, pass_name: str) -> None:
|
|
120
|
+
"""Append a pass name to this adjoint's provenance history."""
|
|
121
|
+
self.history.append(pass_name)
|
|
122
|
+
|
|
123
|
+
def __repr__(self) -> str: # pragma: no cover - cosmetic
|
|
124
|
+
return (
|
|
125
|
+
f"AdjointState(layer_id={self.layer_id}, block={self.block!r}, "
|
|
126
|
+
f"shape={tuple(self.tensor.shape)}, dtype={self.dtype}, "
|
|
127
|
+
f"device={self.device}, history={self.history})"
|
|
128
|
+
)
|
eae_runtime/backend.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Backend Manager: responsible for choosing the execution backend. Blocks can
|
|
3
|
+
override backend selection by carrying a `.eae_backend` attribute naming a
|
|
4
|
+
preferred backend.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import contextlib
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
import torch
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BackendManager:
|
|
16
|
+
SUPPORTED = ("cpu", "cuda", "triton", "rocm", "auto")
|
|
17
|
+
|
|
18
|
+
def __init__(self, backend: str = "auto"):
|
|
19
|
+
if backend not in self.SUPPORTED:
|
|
20
|
+
raise ValueError(f"Unknown backend '{backend}', choices: {self.SUPPORTED}")
|
|
21
|
+
self.requested = backend
|
|
22
|
+
self.resolved = self._resolve(backend)
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def _resolve(backend: str) -> str:
|
|
26
|
+
if backend == "auto":
|
|
27
|
+
return "cuda" if torch.cuda.is_available() else "cpu"
|
|
28
|
+
if backend == "cuda" and not torch.cuda.is_available():
|
|
29
|
+
return "cpu" # graceful fallback rather than a hard failure
|
|
30
|
+
if backend in ("triton", "rocm"):
|
|
31
|
+
# Triton/ROCm kernels are launched *through* CUDA devices in this
|
|
32
|
+
# runtime; if no CUDA device is present we fall back to plain CPU
|
|
33
|
+
# execution so the same code runs everywhere.
|
|
34
|
+
return "cuda" if torch.cuda.is_available() else "cpu"
|
|
35
|
+
return backend
|
|
36
|
+
|
|
37
|
+
def device_for(self, block=None) -> torch.device:
|
|
38
|
+
override = getattr(block, "eae_backend", None)
|
|
39
|
+
backend = self._resolve(override) if override else self.resolved
|
|
40
|
+
return torch.device(backend if backend in ("cpu", "cuda") else "cpu")
|
|
41
|
+
|
|
42
|
+
@contextlib.contextmanager
|
|
43
|
+
def autocast(self, enabled: bool = False, dtype: Optional[torch.dtype] = None):
|
|
44
|
+
device_type = "cuda" if self.resolved == "cuda" else "cpu"
|
|
45
|
+
if not enabled:
|
|
46
|
+
yield
|
|
47
|
+
return
|
|
48
|
+
with torch.autocast(device_type=device_type, dtype=dtype or torch.float16, enabled=True):
|
|
49
|
+
yield
|
|
50
|
+
|
|
51
|
+
def __repr__(self) -> str: # pragma: no cover
|
|
52
|
+
return f"BackendManager(requested={self.requested!r}, resolved={self.resolved!r})"
|
eae_runtime/blocks.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Block interface. The runtime reconstructs blocks independently - no block
|
|
3
|
+
should know anything about global training.
|
|
4
|
+
|
|
5
|
+
Per the spec's non-goals, we do NOT attempt automatic partitioning of
|
|
6
|
+
arbitrary nn.Module graphs. Manual / helper-based block specification is
|
|
7
|
+
sufficient: users hand us a `nn.Sequential` (or any list of `nn.Module`s
|
|
8
|
+
that compose linearly, e.g. repeated Transformer blocks) and we treat each
|
|
9
|
+
element as one reconstructable unit.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import List, Sequence
|
|
15
|
+
|
|
16
|
+
import torch.nn as nn
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class EAEBlock(nn.Module):
|
|
20
|
+
"""Optional marker base class for a block usable by the EAE runtime.
|
|
21
|
+
|
|
22
|
+
Subclassing this is *not* required - any `nn.Module` whose `forward`
|
|
23
|
+
is a pure function of its input and its own parameters works fine.
|
|
24
|
+
Subclass it when you want to be explicit about intent, or want the
|
|
25
|
+
stricter contract this class documents:
|
|
26
|
+
|
|
27
|
+
* forward(x) -> Tensor (single tensor in, single tensor out)
|
|
28
|
+
* parameters() -> Iterator[Tensor] (inherited from nn.Module)
|
|
29
|
+
* no reliance on global/mutable state outside the block
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def forward(self, x): # pragma: no cover - documentation stub
|
|
33
|
+
raise NotImplementedError
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class BlockDecomposer:
|
|
37
|
+
"""Turns a user model into an ordered list of reconstructable blocks."""
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def from_sequential(seq: nn.Sequential) -> List[nn.Module]:
|
|
41
|
+
return list(seq.children())
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def from_list(modules: Sequence[nn.Module]) -> List[nn.Module]:
|
|
45
|
+
return list(modules)
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def decompose(model) -> List[nn.Module]:
|
|
49
|
+
"""Best-effort dispatcher: nn.Sequential -> children, nn.ModuleList ->
|
|
50
|
+
children, list-like -> list, single nn.Module -> a one-block model.
|
|
51
|
+
|
|
52
|
+
`nn.ModuleList` is handled explicitly (not just via generic
|
|
53
|
+
`nn.Module` iteration) because it's the idiomatic way to hold a
|
|
54
|
+
repeated Transformer block stack, e.g.
|
|
55
|
+
`nn.ModuleList([TransformerBlock(...) for _ in range(depth)])`.
|
|
56
|
+
"""
|
|
57
|
+
if isinstance(model, nn.Sequential):
|
|
58
|
+
return BlockDecomposer.from_sequential(model)
|
|
59
|
+
if isinstance(model, nn.ModuleList):
|
|
60
|
+
return list(model)
|
|
61
|
+
if isinstance(model, (list, tuple)):
|
|
62
|
+
return BlockDecomposer.from_list(model)
|
|
63
|
+
if isinstance(model, nn.Module):
|
|
64
|
+
return [model]
|
|
65
|
+
raise TypeError(
|
|
66
|
+
f"Cannot decompose object of type {type(model)} into EAE blocks. "
|
|
67
|
+
"Pass an nn.Sequential, an nn.ModuleList, a list of nn.Module, "
|
|
68
|
+
"or a single nn.Module."
|
|
69
|
+
)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Boundary Store: stores only x0, x1, ..., xL (block-boundary activations).
|
|
3
|
+
Nothing else - no internal activations, no autograd graph.
|
|
4
|
+
|
|
5
|
+
Supports CPU offload, pinned memory, optional compression and configurable
|
|
6
|
+
storage precision, all orthogonal to the reconstruction logic above it.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Dict, Optional
|
|
12
|
+
|
|
13
|
+
import torch
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BoundaryStore:
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
offload: bool = False,
|
|
20
|
+
precision: Optional[torch.dtype] = None,
|
|
21
|
+
pin_memory: bool = False,
|
|
22
|
+
):
|
|
23
|
+
self.offload = offload
|
|
24
|
+
self.precision = precision
|
|
25
|
+
self.pin_memory = pin_memory and torch.cuda.is_available()
|
|
26
|
+
self._store: Dict[int, torch.Tensor] = {}
|
|
27
|
+
self._orig_dtype: Dict[int, torch.dtype] = {}
|
|
28
|
+
self._orig_device: Dict[int, torch.device] = {}
|
|
29
|
+
|
|
30
|
+
def put(self, idx: int, tensor: torch.Tensor) -> None:
|
|
31
|
+
t = tensor.detach()
|
|
32
|
+
self._orig_dtype[idx] = t.dtype
|
|
33
|
+
self._orig_device[idx] = t.device
|
|
34
|
+
|
|
35
|
+
if self.precision is not None:
|
|
36
|
+
t = t.to(self.precision)
|
|
37
|
+
if self.offload:
|
|
38
|
+
t = t.to("cpu")
|
|
39
|
+
if self.pin_memory:
|
|
40
|
+
t = t.pin_memory()
|
|
41
|
+
else:
|
|
42
|
+
t = t.clone() # own our copy, independent of caller's buffer
|
|
43
|
+
self._store[idx] = t
|
|
44
|
+
|
|
45
|
+
def get(self, idx: int, device: Optional[torch.device] = None, dtype: Optional[torch.dtype] = None) -> torch.Tensor:
|
|
46
|
+
if idx not in self._store:
|
|
47
|
+
raise KeyError(f"Boundary {idx} was never stored")
|
|
48
|
+
t = self._store[idx]
|
|
49
|
+
target_device = device if device is not None else self._orig_device[idx]
|
|
50
|
+
target_dtype = dtype if dtype is not None else self._orig_dtype[idx]
|
|
51
|
+
return t.to(device=target_device, dtype=target_dtype)
|
|
52
|
+
|
|
53
|
+
def __contains__(self, idx: int) -> bool:
|
|
54
|
+
return idx in self._store
|
|
55
|
+
|
|
56
|
+
def __len__(self) -> int:
|
|
57
|
+
return len(self._store)
|
|
58
|
+
|
|
59
|
+
def clear(self) -> None:
|
|
60
|
+
self._store.clear()
|
|
61
|
+
self._orig_dtype.clear()
|
|
62
|
+
self._orig_device.clear()
|
|
63
|
+
|
|
64
|
+
def memory_bytes(self) -> int:
|
|
65
|
+
return sum(t.element_size() * t.nelement() for t in self._store.values())
|
eae_runtime/config.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Single configuration object for the runtime. No scattered options.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import Any, List, Optional, Union
|
|
9
|
+
|
|
10
|
+
import torch
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class RuntimeConfig:
|
|
15
|
+
# scheduler: name string ("sequential" | "async" | "pipeline" | "distributed")
|
|
16
|
+
# or a BaseScheduler instance for full user control.
|
|
17
|
+
scheduler: Union[str, Any] = "sequential"
|
|
18
|
+
|
|
19
|
+
# memory policy: name string ("pool" | "none") or a MemoryPolicy instance.
|
|
20
|
+
memory: Union[str, Any] = "pool"
|
|
21
|
+
|
|
22
|
+
# backend: name string ("cpu" | "cuda" | "triton" | "rocm") or a
|
|
23
|
+
# BackendManager instance.
|
|
24
|
+
backend: Union[str, Any] = "auto"
|
|
25
|
+
|
|
26
|
+
# ordered list of EAEPass instances forming the adjoint pipeline.
|
|
27
|
+
passes: List[Any] = field(default_factory=list)
|
|
28
|
+
|
|
29
|
+
# mixed precision compute dtype used during reconstruction (None = fp32).
|
|
30
|
+
compute_dtype: Optional[torch.dtype] = None
|
|
31
|
+
|
|
32
|
+
# boundary store options
|
|
33
|
+
boundary_offload: bool = False # move boundary activations to CPU
|
|
34
|
+
boundary_precision: Optional[torch.dtype] = None # e.g. torch.float16 to save memory
|
|
35
|
+
pin_memory: bool = False
|
|
36
|
+
|
|
37
|
+
# pipeline scheduler option
|
|
38
|
+
num_microbatches: int = 1
|
|
39
|
+
|
|
40
|
+
# misc
|
|
41
|
+
grad_clip_norm: Optional[float] = None
|
|
42
|
+
seed: Optional[int] = None
|
|
43
|
+
enable_profiler: bool = True
|
|
44
|
+
log_level: str = "WARNING"
|
|
45
|
+
|
|
46
|
+
def __post_init__(self):
|
|
47
|
+
if self.seed is not None:
|
|
48
|
+
torch.manual_seed(self.seed)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Optional, opt-in reference implementations built on top of the EAE Runtime
|
|
3
|
+
core. Nothing under `eae_runtime.contrib` is imported by
|
|
4
|
+
`eae_runtime.__init__`, and the runtime core has zero dependency on it -
|
|
5
|
+
this package exists purely to demonstrate and give you a starting point
|
|
6
|
+
for real Transformer architectures. Fork freely.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .transformer_blocks import (
|
|
10
|
+
CausalSelfAttention,
|
|
11
|
+
PreNormTransformerBlock,
|
|
12
|
+
RMSNorm,
|
|
13
|
+
RotaryPositionalEmbedding,
|
|
14
|
+
SwiGLU,
|
|
15
|
+
apply_rotary,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"CausalSelfAttention",
|
|
20
|
+
"PreNormTransformerBlock",
|
|
21
|
+
"RMSNorm",
|
|
22
|
+
"RotaryPositionalEmbedding",
|
|
23
|
+
"SwiGLU",
|
|
24
|
+
"apply_rotary",
|
|
25
|
+
]
|