torch-harness 0.10.1__tar.gz
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.
- torch_harness-0.10.1/LICENSE +21 -0
- torch_harness-0.10.1/PKG-INFO +236 -0
- torch_harness-0.10.1/README.md +204 -0
- torch_harness-0.10.1/pyproject.toml +58 -0
- torch_harness-0.10.1/pyproject.toml.orig +52 -0
- torch_harness-0.10.1/src/torch_harness/__init__.py +27 -0
- torch_harness-0.10.1/src/torch_harness/layers/__init__.py +9 -0
- torch_harness-0.10.1/src/torch_harness/layers/safe_batch_norm/DEVLOG.md +64 -0
- torch_harness-0.10.1/src/torch_harness/layers/safe_batch_norm/README.md +43 -0
- torch_harness-0.10.1/src/torch_harness/layers/safe_batch_norm/__init__.py +9 -0
- torch_harness-0.10.1/src/torch_harness/layers/safe_batch_norm/safe_batch_norm.py +108 -0
- torch_harness-0.10.1/src/torch_harness/lightning/__init__.py +9 -0
- torch_harness-0.10.1/src/torch_harness/lightning/fault_tolerant.py +241 -0
- torch_harness-0.10.1/src/torch_harness/lightning/file_model_structure.py +58 -0
- torch_harness-0.10.1/src/torch_harness/losses/__init__.py +19 -0
- torch_harness-0.10.1/src/torch_harness/losses/adaptive_balancing/DEVLOG.md +34 -0
- torch_harness-0.10.1/src/torch_harness/losses/adaptive_balancing/README.md +148 -0
- torch_harness-0.10.1/src/torch_harness/losses/adaptive_balancing/__init__.py +20 -0
- torch_harness-0.10.1/src/torch_harness/losses/adaptive_balancing/config.py +52 -0
- torch_harness-0.10.1/src/torch_harness/losses/adaptive_balancing/controller.py +392 -0
- torch_harness-0.10.1/src/torch_harness/losses/adaptive_balancing/measurement.py +130 -0
- torch_harness-0.10.1/src/torch_harness/losses/adaptive_balancing/numerics.py +25 -0
- torch_harness-0.10.1/src/torch_harness/losses/super_loss/DEVLOG.md +46 -0
- torch_harness-0.10.1/src/torch_harness/losses/super_loss/README.md +76 -0
- torch_harness-0.10.1/src/torch_harness/losses/super_loss/__init__.py +5 -0
- torch_harness-0.10.1/src/torch_harness/losses/super_loss/super_loss.py +142 -0
- torch_harness-0.10.1/src/torch_harness/losses/uncertainty_weighting/DEVLOG.md +40 -0
- torch_harness-0.10.1/src/torch_harness/losses/uncertainty_weighting/README.md +74 -0
- torch_harness-0.10.1/src/torch_harness/losses/uncertainty_weighting/__init__.py +7 -0
- torch_harness-0.10.1/src/torch_harness/losses/uncertainty_weighting/uncertainty_weighting.py +126 -0
- torch_harness-0.10.1/src/torch_harness/onnx/DEVLOG.md +42 -0
- torch_harness-0.10.1/src/torch_harness/onnx/README.md +86 -0
- torch_harness-0.10.1/src/torch_harness/onnx/__init__.py +6 -0
- torch_harness-0.10.1/src/torch_harness/onnx/_io.py +41 -0
- torch_harness-0.10.1/src/torch_harness/onnx/_text_graph.py +345 -0
- torch_harness-0.10.1/src/torch_harness/py.typed +0 -0
- torch_harness-0.10.1/src/torch_harness/runtime/README.md +28 -0
- torch_harness-0.10.1/src/torch_harness/runtime/__init__.py +5 -0
- torch_harness-0.10.1/src/torch_harness/runtime/replace_layers.py +70 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vadym Stupakov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: torch-harness
|
|
3
|
+
Version: 0.10.1
|
|
4
|
+
Summary: Reliable utilities for PyTorch and Lightning
|
|
5
|
+
Keywords: lightning,machine-learning,pytorch,torch
|
|
6
|
+
Author: Vadym Stupakov
|
|
7
|
+
Author-email: Vadym Stupakov <vadim.stupakov@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Dist: jaxtyping>=0.3.11
|
|
21
|
+
Requires-Dist: loguru>=0.7
|
|
22
|
+
Requires-Dist: torch>=2.0
|
|
23
|
+
Requires-Dist: typing-extensions>=4.4
|
|
24
|
+
Requires-Dist: lightning>=2.6,<3 ; extra == 'lightning'
|
|
25
|
+
Requires-Dist: onnx>=1.18 ; extra == 'onnx'
|
|
26
|
+
Requires-Python: >=3.11
|
|
27
|
+
Project-URL: Repository, https://github.com/Red-Eyed/torch-harness
|
|
28
|
+
Project-URL: Issues, https://github.com/Red-Eyed/torch-harness/issues
|
|
29
|
+
Provides-Extra: lightning
|
|
30
|
+
Provides-Extra: onnx
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# torch-harness
|
|
34
|
+
|
|
35
|
+
An intentionally uncategorized collection of utilities for PyTorch modeling
|
|
36
|
+
experiments. It is a toolbox rather than a single framework: utilities are added
|
|
37
|
+
as experimental needs arise, without forcing them into an artificial hierarchy.
|
|
38
|
+
|
|
39
|
+
Each utility is modular and self-contained. It owns its implementation and tests,
|
|
40
|
+
avoids assumptions about the surrounding project, and can be used independently
|
|
41
|
+
of the other utilities. Optional framework integrations remain isolated so they
|
|
42
|
+
do not add dependencies to the core package.
|
|
43
|
+
|
|
44
|
+
## Adaptive loss balancing
|
|
45
|
+
|
|
46
|
+
`LossBalancer` adapts named auxiliary weights from shared gradient norms while
|
|
47
|
+
keeping the primary loss weight exactly one:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from torch_harness.losses import LossBalancer, LossTarget, weighted_loss
|
|
51
|
+
|
|
52
|
+
balancer = LossBalancer(
|
|
53
|
+
reference="primary",
|
|
54
|
+
adaptive={"auxiliary": LossTarget(ratio=0.3, initial_weight=0.1)},
|
|
55
|
+
)
|
|
56
|
+
weights = balancer.update(losses, shared=shared_features, step=global_step)
|
|
57
|
+
total_loss = weighted_loss(losses, weights)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
It supports independent targets, fixed weights, optional losses, periodic EMA
|
|
61
|
+
updates, checkpoint continuation, and synchronized DDP activation measurements.
|
|
62
|
+
See the [component documentation](src/torch_harness/losses/adaptive_balancing/README.md)
|
|
63
|
+
for stability controls, diagnostics, and training integration. Run
|
|
64
|
+
`uv run examples/adaptive_loss_balancing.py` for a synthetic optimization example.
|
|
65
|
+
|
|
66
|
+
## Uncertainty weighting
|
|
67
|
+
|
|
68
|
+
`UncertaintyWeighting` learns positive weights for a vector of peer task losses:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
import torch
|
|
72
|
+
|
|
73
|
+
from torch_harness.losses import UncertaintyWeighting
|
|
74
|
+
|
|
75
|
+
weighting = UncertaintyWeighting(num_tasks=3)
|
|
76
|
+
task_losses = torch.stack((classification_loss, depth_loss, normal_loss))
|
|
77
|
+
loss = weighting(task_losses)
|
|
78
|
+
weights_for_logging = weighting.effective_weights
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Each effective weight starts near one and adapts jointly with the model. The
|
|
82
|
+
module preserves gradients to the input task losses and its uncertainty
|
|
83
|
+
parameters, while the weights exposed for logging are detached. See the
|
|
84
|
+
[component documentation](src/torch_harness/losses/uncertainty_weighting/README.md)
|
|
85
|
+
for the exact objective, optimizer wiring, numerical behavior, and limitations.
|
|
86
|
+
|
|
87
|
+
## SuperLoss
|
|
88
|
+
|
|
89
|
+
`SuperLoss` applies robust curriculum weighting to any unreduced task loss:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from math import log
|
|
93
|
+
|
|
94
|
+
import torch.nn.functional as F
|
|
95
|
+
|
|
96
|
+
from torch_harness.losses import SuperLoss
|
|
97
|
+
|
|
98
|
+
criterion = SuperLoss(threshold=log(10), regularization=1.0)
|
|
99
|
+
task_loss = F.cross_entropy(logits, targets, reduction="none")
|
|
100
|
+
loss = criterion(task_loss)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
It computes the closed-form optimal sample confidence from the SuperLoss paper,
|
|
104
|
+
upweighting easy samples and downweighting hard samples. The implementation is
|
|
105
|
+
task-agnostic, runs entirely in PyTorch, and has no SciPy or per-sample-state
|
|
106
|
+
dependency. See the [component documentation](src/torch_harness/losses/super_loss/README.md)
|
|
107
|
+
for parameter guidance and numerical details.
|
|
108
|
+
|
|
109
|
+
## SafeBatchNorm
|
|
110
|
+
|
|
111
|
+
`SafeBatchNorm` rejects a non-finite activation before it can corrupt BatchNorm
|
|
112
|
+
running statistics.
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from torch_harness.layers import SafeBatchNorm2d
|
|
116
|
+
|
|
117
|
+
normalization = SafeBatchNorm2d(64)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`SafeBatchNorm1d`, `SafeBatchNorm2d`, and `SafeBatchNorm3d` directly inherit
|
|
121
|
+
their matching PyTorch classes. They retain the native constructor, state-dict
|
|
122
|
+
layout, and type identity. A non-finite input raises `FloatingPointError`, which
|
|
123
|
+
a fault-tolerant training loop can catch to skip the step.
|
|
124
|
+
|
|
125
|
+
`nn.SyncBatchNorm` is intentionally unsupported because a rank-local failure
|
|
126
|
+
before its collective could deadlock the other ranks.
|
|
127
|
+
|
|
128
|
+
## Runtime layer replacement
|
|
129
|
+
|
|
130
|
+
`replace_layers` recursively transforms existing models through an explicit
|
|
131
|
+
replacement factory:
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from torch import nn
|
|
135
|
+
|
|
136
|
+
from torch_harness.runtime import replace_layers
|
|
137
|
+
|
|
138
|
+
model = replace_layers(
|
|
139
|
+
model,
|
|
140
|
+
old_layer_cls=nn.SiLU,
|
|
141
|
+
replacement_factory=lambda silu: nn.ReLU(inplace=silu.inplace),
|
|
142
|
+
)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The traversal includes nested containers such as `Sequential`. Run replacement
|
|
146
|
+
before constructing the optimizer, distributed wrappers, or a compiled model.
|
|
147
|
+
|
|
148
|
+
## Fault-tolerant training steps
|
|
149
|
+
|
|
150
|
+
`FaultTolerantTrainingStep` skips occasional exceptions and NaN or infinite losses
|
|
151
|
+
before Lightning automatic optimization runs backward or updates the optimizer:
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
from datetime import timedelta
|
|
155
|
+
|
|
156
|
+
from lightning.pytorch import Trainer
|
|
157
|
+
from torch_harness.lightning import FaultTolerantTrainingStep
|
|
158
|
+
|
|
159
|
+
fault_tolerance = FaultTolerantTrainingStep(
|
|
160
|
+
max_faults=3,
|
|
161
|
+
fault_window=timedelta(hours=1),
|
|
162
|
+
)
|
|
163
|
+
trainer = Trainer(callbacks=[fault_tolerance])
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
This example skips the first three faults in any rolling one-hour window. A fourth
|
|
167
|
+
fault within that window is logged through Loguru and propagated. Exceptions retain
|
|
168
|
+
their original type and traceback; repeated non-finite losses raise
|
|
169
|
+
`NonFiniteLossError`.
|
|
170
|
+
|
|
171
|
+
Install the Lightning integration with `torch-harness[lightning]`.
|
|
172
|
+
|
|
173
|
+
The callback intentionally supports only single-process automatic optimization.
|
|
174
|
+
Errors raised during backward or `optimizer.step` happen after the recoverable
|
|
175
|
+
boundary and are propagated because an optimizer update may already be partial.
|
|
176
|
+
|
|
177
|
+
## Model structures in files
|
|
178
|
+
|
|
179
|
+
`FileModelStructure` saves the recursive structure produced by `print(model)` as
|
|
180
|
+
readable UTF-8 text when fitting starts:
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
from pathlib import Path
|
|
184
|
+
|
|
185
|
+
from lightning.pytorch import Trainer
|
|
186
|
+
from torch_harness.lightning import FileModelStructure
|
|
187
|
+
|
|
188
|
+
structure = FileModelStructure(
|
|
189
|
+
output_path=Path("artifacts/model-structure.txt"),
|
|
190
|
+
)
|
|
191
|
+
trainer = Trainer(callbacks=[structure])
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The callback includes the complete registered module hierarchy without requiring
|
|
195
|
+
example inputs or running a forward pass. It creates parent directories, replaces
|
|
196
|
+
an existing structure file, and writes only from the global-zero process. Runtime
|
|
197
|
+
tensor operations that are not registered modules do not appear.
|
|
198
|
+
|
|
199
|
+
Run `uv run examples/file_model_structure.py` to generate an inspectable example
|
|
200
|
+
at `examples/model-structure.txt`.
|
|
201
|
+
|
|
202
|
+
## Human-readable ONNX graphs
|
|
203
|
+
|
|
204
|
+
Install `torch-harness[onnx]` to save module summaries suitable for Git diffs
|
|
205
|
+
and Meld:
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
from pathlib import Path
|
|
209
|
+
|
|
210
|
+
import onnx
|
|
211
|
+
|
|
212
|
+
from torch_harness.onnx import write_text_graph
|
|
213
|
+
|
|
214
|
+
model = onnx.load("model.onnx")
|
|
215
|
+
write_text_graph(model, Path("artifacts/model.onnx.txt"))
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Summaries show operation signatures and counts, attributes, small constants,
|
|
219
|
+
parameter shapes, and input/output dimensions. They omit temporary node indexes
|
|
220
|
+
and weight payloads, making RMSNorm decomposition versus `RMSNormalization`
|
|
221
|
+
visible without renumbering unrelated operations. Internal wiring is omitted.
|
|
222
|
+
|
|
223
|
+
See the [ONNX documentation](src/torch_harness/onnx/README.md) for source module
|
|
224
|
+
capture, shape inference, and format limitations. Run
|
|
225
|
+
`uv run --extra onnx examples/onnx_text_graph.py` for a small example.
|
|
226
|
+
|
|
227
|
+
## Planned utilities
|
|
228
|
+
|
|
229
|
+
- A Lightning `PreciseBN` callback for recomputing BatchNorm running statistics.
|
|
230
|
+
- A Lightning mixin that manages schedule-free AdamW train/evaluation state.
|
|
231
|
+
|
|
232
|
+
Each integration will be isolated so users only install the frameworks they need.
|
|
233
|
+
|
|
234
|
+
## License
|
|
235
|
+
|
|
236
|
+
`torch-harness` is released under the MIT License.
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# torch-harness
|
|
2
|
+
|
|
3
|
+
An intentionally uncategorized collection of utilities for PyTorch modeling
|
|
4
|
+
experiments. It is a toolbox rather than a single framework: utilities are added
|
|
5
|
+
as experimental needs arise, without forcing them into an artificial hierarchy.
|
|
6
|
+
|
|
7
|
+
Each utility is modular and self-contained. It owns its implementation and tests,
|
|
8
|
+
avoids assumptions about the surrounding project, and can be used independently
|
|
9
|
+
of the other utilities. Optional framework integrations remain isolated so they
|
|
10
|
+
do not add dependencies to the core package.
|
|
11
|
+
|
|
12
|
+
## Adaptive loss balancing
|
|
13
|
+
|
|
14
|
+
`LossBalancer` adapts named auxiliary weights from shared gradient norms while
|
|
15
|
+
keeping the primary loss weight exactly one:
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from torch_harness.losses import LossBalancer, LossTarget, weighted_loss
|
|
19
|
+
|
|
20
|
+
balancer = LossBalancer(
|
|
21
|
+
reference="primary",
|
|
22
|
+
adaptive={"auxiliary": LossTarget(ratio=0.3, initial_weight=0.1)},
|
|
23
|
+
)
|
|
24
|
+
weights = balancer.update(losses, shared=shared_features, step=global_step)
|
|
25
|
+
total_loss = weighted_loss(losses, weights)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
It supports independent targets, fixed weights, optional losses, periodic EMA
|
|
29
|
+
updates, checkpoint continuation, and synchronized DDP activation measurements.
|
|
30
|
+
See the [component documentation](src/torch_harness/losses/adaptive_balancing/README.md)
|
|
31
|
+
for stability controls, diagnostics, and training integration. Run
|
|
32
|
+
`uv run examples/adaptive_loss_balancing.py` for a synthetic optimization example.
|
|
33
|
+
|
|
34
|
+
## Uncertainty weighting
|
|
35
|
+
|
|
36
|
+
`UncertaintyWeighting` learns positive weights for a vector of peer task losses:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import torch
|
|
40
|
+
|
|
41
|
+
from torch_harness.losses import UncertaintyWeighting
|
|
42
|
+
|
|
43
|
+
weighting = UncertaintyWeighting(num_tasks=3)
|
|
44
|
+
task_losses = torch.stack((classification_loss, depth_loss, normal_loss))
|
|
45
|
+
loss = weighting(task_losses)
|
|
46
|
+
weights_for_logging = weighting.effective_weights
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Each effective weight starts near one and adapts jointly with the model. The
|
|
50
|
+
module preserves gradients to the input task losses and its uncertainty
|
|
51
|
+
parameters, while the weights exposed for logging are detached. See the
|
|
52
|
+
[component documentation](src/torch_harness/losses/uncertainty_weighting/README.md)
|
|
53
|
+
for the exact objective, optimizer wiring, numerical behavior, and limitations.
|
|
54
|
+
|
|
55
|
+
## SuperLoss
|
|
56
|
+
|
|
57
|
+
`SuperLoss` applies robust curriculum weighting to any unreduced task loss:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from math import log
|
|
61
|
+
|
|
62
|
+
import torch.nn.functional as F
|
|
63
|
+
|
|
64
|
+
from torch_harness.losses import SuperLoss
|
|
65
|
+
|
|
66
|
+
criterion = SuperLoss(threshold=log(10), regularization=1.0)
|
|
67
|
+
task_loss = F.cross_entropy(logits, targets, reduction="none")
|
|
68
|
+
loss = criterion(task_loss)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
It computes the closed-form optimal sample confidence from the SuperLoss paper,
|
|
72
|
+
upweighting easy samples and downweighting hard samples. The implementation is
|
|
73
|
+
task-agnostic, runs entirely in PyTorch, and has no SciPy or per-sample-state
|
|
74
|
+
dependency. See the [component documentation](src/torch_harness/losses/super_loss/README.md)
|
|
75
|
+
for parameter guidance and numerical details.
|
|
76
|
+
|
|
77
|
+
## SafeBatchNorm
|
|
78
|
+
|
|
79
|
+
`SafeBatchNorm` rejects a non-finite activation before it can corrupt BatchNorm
|
|
80
|
+
running statistics.
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from torch_harness.layers import SafeBatchNorm2d
|
|
84
|
+
|
|
85
|
+
normalization = SafeBatchNorm2d(64)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`SafeBatchNorm1d`, `SafeBatchNorm2d`, and `SafeBatchNorm3d` directly inherit
|
|
89
|
+
their matching PyTorch classes. They retain the native constructor, state-dict
|
|
90
|
+
layout, and type identity. A non-finite input raises `FloatingPointError`, which
|
|
91
|
+
a fault-tolerant training loop can catch to skip the step.
|
|
92
|
+
|
|
93
|
+
`nn.SyncBatchNorm` is intentionally unsupported because a rank-local failure
|
|
94
|
+
before its collective could deadlock the other ranks.
|
|
95
|
+
|
|
96
|
+
## Runtime layer replacement
|
|
97
|
+
|
|
98
|
+
`replace_layers` recursively transforms existing models through an explicit
|
|
99
|
+
replacement factory:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from torch import nn
|
|
103
|
+
|
|
104
|
+
from torch_harness.runtime import replace_layers
|
|
105
|
+
|
|
106
|
+
model = replace_layers(
|
|
107
|
+
model,
|
|
108
|
+
old_layer_cls=nn.SiLU,
|
|
109
|
+
replacement_factory=lambda silu: nn.ReLU(inplace=silu.inplace),
|
|
110
|
+
)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The traversal includes nested containers such as `Sequential`. Run replacement
|
|
114
|
+
before constructing the optimizer, distributed wrappers, or a compiled model.
|
|
115
|
+
|
|
116
|
+
## Fault-tolerant training steps
|
|
117
|
+
|
|
118
|
+
`FaultTolerantTrainingStep` skips occasional exceptions and NaN or infinite losses
|
|
119
|
+
before Lightning automatic optimization runs backward or updates the optimizer:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from datetime import timedelta
|
|
123
|
+
|
|
124
|
+
from lightning.pytorch import Trainer
|
|
125
|
+
from torch_harness.lightning import FaultTolerantTrainingStep
|
|
126
|
+
|
|
127
|
+
fault_tolerance = FaultTolerantTrainingStep(
|
|
128
|
+
max_faults=3,
|
|
129
|
+
fault_window=timedelta(hours=1),
|
|
130
|
+
)
|
|
131
|
+
trainer = Trainer(callbacks=[fault_tolerance])
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
This example skips the first three faults in any rolling one-hour window. A fourth
|
|
135
|
+
fault within that window is logged through Loguru and propagated. Exceptions retain
|
|
136
|
+
their original type and traceback; repeated non-finite losses raise
|
|
137
|
+
`NonFiniteLossError`.
|
|
138
|
+
|
|
139
|
+
Install the Lightning integration with `torch-harness[lightning]`.
|
|
140
|
+
|
|
141
|
+
The callback intentionally supports only single-process automatic optimization.
|
|
142
|
+
Errors raised during backward or `optimizer.step` happen after the recoverable
|
|
143
|
+
boundary and are propagated because an optimizer update may already be partial.
|
|
144
|
+
|
|
145
|
+
## Model structures in files
|
|
146
|
+
|
|
147
|
+
`FileModelStructure` saves the recursive structure produced by `print(model)` as
|
|
148
|
+
readable UTF-8 text when fitting starts:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from pathlib import Path
|
|
152
|
+
|
|
153
|
+
from lightning.pytorch import Trainer
|
|
154
|
+
from torch_harness.lightning import FileModelStructure
|
|
155
|
+
|
|
156
|
+
structure = FileModelStructure(
|
|
157
|
+
output_path=Path("artifacts/model-structure.txt"),
|
|
158
|
+
)
|
|
159
|
+
trainer = Trainer(callbacks=[structure])
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The callback includes the complete registered module hierarchy without requiring
|
|
163
|
+
example inputs or running a forward pass. It creates parent directories, replaces
|
|
164
|
+
an existing structure file, and writes only from the global-zero process. Runtime
|
|
165
|
+
tensor operations that are not registered modules do not appear.
|
|
166
|
+
|
|
167
|
+
Run `uv run examples/file_model_structure.py` to generate an inspectable example
|
|
168
|
+
at `examples/model-structure.txt`.
|
|
169
|
+
|
|
170
|
+
## Human-readable ONNX graphs
|
|
171
|
+
|
|
172
|
+
Install `torch-harness[onnx]` to save module summaries suitable for Git diffs
|
|
173
|
+
and Meld:
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
from pathlib import Path
|
|
177
|
+
|
|
178
|
+
import onnx
|
|
179
|
+
|
|
180
|
+
from torch_harness.onnx import write_text_graph
|
|
181
|
+
|
|
182
|
+
model = onnx.load("model.onnx")
|
|
183
|
+
write_text_graph(model, Path("artifacts/model.onnx.txt"))
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Summaries show operation signatures and counts, attributes, small constants,
|
|
187
|
+
parameter shapes, and input/output dimensions. They omit temporary node indexes
|
|
188
|
+
and weight payloads, making RMSNorm decomposition versus `RMSNormalization`
|
|
189
|
+
visible without renumbering unrelated operations. Internal wiring is omitted.
|
|
190
|
+
|
|
191
|
+
See the [ONNX documentation](src/torch_harness/onnx/README.md) for source module
|
|
192
|
+
capture, shape inference, and format limitations. Run
|
|
193
|
+
`uv run --extra onnx examples/onnx_text_graph.py` for a small example.
|
|
194
|
+
|
|
195
|
+
## Planned utilities
|
|
196
|
+
|
|
197
|
+
- A Lightning `PreciseBN` callback for recomputing BatchNorm running statistics.
|
|
198
|
+
- A Lightning mixin that manages schedule-free AdamW train/evaluation state.
|
|
199
|
+
|
|
200
|
+
Each integration will be isolated so users only install the frameworks they need.
|
|
201
|
+
|
|
202
|
+
## License
|
|
203
|
+
|
|
204
|
+
`torch-harness` is released under the MIT License.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "torch-harness"
|
|
3
|
+
version = "0.10.1"
|
|
4
|
+
description = "Reliable utilities for PyTorch and Lightning"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"jaxtyping>=0.3.11",
|
|
9
|
+
"loguru>=0.7",
|
|
10
|
+
"torch>=2.0",
|
|
11
|
+
"typing-extensions>=4.4",
|
|
12
|
+
]
|
|
13
|
+
license = "MIT"
|
|
14
|
+
license-files = ["LICENSE"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Intended Audience :: Science/Research",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Programming Language :: Python :: 3.14",
|
|
25
|
+
"Typing :: Typed",
|
|
26
|
+
]
|
|
27
|
+
keywords = [
|
|
28
|
+
"lightning",
|
|
29
|
+
"machine-learning",
|
|
30
|
+
"pytorch",
|
|
31
|
+
"torch",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[[project.authors]]
|
|
35
|
+
name = "Vadym Stupakov"
|
|
36
|
+
email = "vadim.stupakov@gmail.com"
|
|
37
|
+
|
|
38
|
+
[project.optional-dependencies]
|
|
39
|
+
lightning = ["lightning>=2.6,<3"]
|
|
40
|
+
onnx = ["onnx>=1.18"]
|
|
41
|
+
|
|
42
|
+
[project.urls]
|
|
43
|
+
Repository = "https://github.com/Red-Eyed/torch-harness"
|
|
44
|
+
Issues = "https://github.com/Red-Eyed/torch-harness/issues"
|
|
45
|
+
|
|
46
|
+
[build-system]
|
|
47
|
+
requires = ["uv_build>=0.12.1,<0.13.0"]
|
|
48
|
+
build-backend = "uv_build"
|
|
49
|
+
|
|
50
|
+
[dependency-groups]
|
|
51
|
+
dev = [
|
|
52
|
+
"lightning>=2.6,<3",
|
|
53
|
+
"onnx>=1.18",
|
|
54
|
+
"pyrefly>=1.2.0",
|
|
55
|
+
"pytest>=9.1.1",
|
|
56
|
+
"ruff>=0.16.4",
|
|
57
|
+
"scipy>=1.17.1",
|
|
58
|
+
]
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "torch-harness"
|
|
3
|
+
version = "0.10.1"
|
|
4
|
+
description = "Reliable utilities for PyTorch and Lightning"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Vadym Stupakov", email = "vadim.stupakov@gmail.com" },
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"jaxtyping>=0.3.11",
|
|
12
|
+
"loguru>=0.7",
|
|
13
|
+
"torch>=2.0",
|
|
14
|
+
"typing-extensions>=4.4",
|
|
15
|
+
]
|
|
16
|
+
license = "MIT"
|
|
17
|
+
license-files = ["LICENSE"]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Development Status :: 3 - Alpha",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"Intended Audience :: Science/Research",
|
|
22
|
+
"Operating System :: OS Independent",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: Python :: 3.13",
|
|
27
|
+
"Programming Language :: Python :: 3.14",
|
|
28
|
+
"Typing :: Typed",
|
|
29
|
+
]
|
|
30
|
+
keywords = ["lightning", "machine-learning", "pytorch", "torch"]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
lightning = ["lightning>=2.6,<3"]
|
|
34
|
+
onnx = ["onnx>=1.18"]
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Repository = "https://github.com/Red-Eyed/torch-harness"
|
|
38
|
+
Issues = "https://github.com/Red-Eyed/torch-harness/issues"
|
|
39
|
+
|
|
40
|
+
[build-system]
|
|
41
|
+
requires = ["uv_build>=0.12.1,<0.13.0"]
|
|
42
|
+
build-backend = "uv_build"
|
|
43
|
+
|
|
44
|
+
[dependency-groups]
|
|
45
|
+
dev = [
|
|
46
|
+
"lightning>=2.6,<3",
|
|
47
|
+
"onnx>=1.18",
|
|
48
|
+
"pyrefly>=1.2.0",
|
|
49
|
+
"pytest>=9.1.1",
|
|
50
|
+
"ruff>=0.16.4",
|
|
51
|
+
"scipy>=1.17.1",
|
|
52
|
+
]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Reliable utilities for PyTorch and Lightning."""
|
|
2
|
+
|
|
3
|
+
from torch_harness.layers import (
|
|
4
|
+
SafeBatchNorm1d,
|
|
5
|
+
SafeBatchNorm2d,
|
|
6
|
+
SafeBatchNorm3d,
|
|
7
|
+
)
|
|
8
|
+
from torch_harness.losses import (
|
|
9
|
+
BalancerConfig,
|
|
10
|
+
LossBalancer,
|
|
11
|
+
LossTarget,
|
|
12
|
+
SuperLoss,
|
|
13
|
+
UncertaintyWeighting,
|
|
14
|
+
weighted_loss,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"BalancerConfig",
|
|
19
|
+
"LossBalancer",
|
|
20
|
+
"LossTarget",
|
|
21
|
+
"SafeBatchNorm1d",
|
|
22
|
+
"SafeBatchNorm2d",
|
|
23
|
+
"SafeBatchNorm3d",
|
|
24
|
+
"SuperLoss",
|
|
25
|
+
"UncertaintyWeighting",
|
|
26
|
+
"weighted_loss",
|
|
27
|
+
]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# SafeBatchNorm development log
|
|
2
|
+
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Motivating observation
|
|
6
|
+
|
|
7
|
+
Models often already contain native BatchNorm layers when fault-tolerant
|
|
8
|
+
training is enabled. Runtime conversion should not rely on changing an
|
|
9
|
+
instance's `__class__`, which skips construction and couples correctness to
|
|
10
|
+
Python object-layout details.
|
|
11
|
+
|
|
12
|
+
### Decision
|
|
13
|
+
|
|
14
|
+
Add explicit `from_batch_norm` constructors that rebuild each safe layer from
|
|
15
|
+
the corresponding native BatchNorm configuration and persistent state. The
|
|
16
|
+
conversion also preserves training mode and affine-parameter `requires_grad`
|
|
17
|
+
settings. Runtime traversal remains independent through a replacement factory.
|
|
18
|
+
|
|
19
|
+
### Verified result
|
|
20
|
+
|
|
21
|
+
Parameterized tests verify that BatchNorm1d, BatchNorm2d, and BatchNorm3d are
|
|
22
|
+
rebuilt as distinct safe instances with equivalent configuration, parameters,
|
|
23
|
+
running statistics, training mode, device, dtype, and `requires_grad` settings.
|
|
24
|
+
The runtime integration also converts nested native BatchNorm layers without
|
|
25
|
+
changing the traversal implementation.
|
|
26
|
+
|
|
27
|
+
### Unproven claims
|
|
28
|
+
|
|
29
|
+
The per-layer scalar check introduces a device-to-host synchronization on
|
|
30
|
+
accelerators; its training-throughput cost has not been benchmarked.
|
|
31
|
+
|
|
32
|
+
## 0.2.0
|
|
33
|
+
|
|
34
|
+
### Motivating observation
|
|
35
|
+
|
|
36
|
+
A fault-tolerant training loop can reject non-finite model outputs, but that
|
|
37
|
+
check occurs after BatchNorm has already updated its persistent running state
|
|
38
|
+
from the faulty activation.
|
|
39
|
+
|
|
40
|
+
### Decision
|
|
41
|
+
|
|
42
|
+
Directly inherit BatchNorm1d, BatchNorm2d, and BatchNorm3d, and raise
|
|
43
|
+
`FloatingPointError` when a shared pre-forward input check finds a NaN or
|
|
44
|
+
infinity. Checking before BatchNorm prevents any running-state mutation and
|
|
45
|
+
leaves PyTorch's forward and backward implementations untouched for finite
|
|
46
|
+
inputs. Inheritance preserves native constructors, state-dict keys, and type
|
|
47
|
+
identity.
|
|
48
|
+
|
|
49
|
+
Use an explicit exception rather than `assert`, because optimized Python can
|
|
50
|
+
remove assertions. Synchronized BatchNorm is excluded because a rank-local
|
|
51
|
+
early failure could deadlock peers already entering its collective.
|
|
52
|
+
|
|
53
|
+
### Verified result
|
|
54
|
+
|
|
55
|
+
Parameterized tests against BatchNorm1d, BatchNorm2d, and BatchNorm3d verify
|
|
56
|
+
that finite outputs, running-state updates, input gradients, and affine
|
|
57
|
+
parameter gradients match native BatchNorm. NaN and infinity inputs raise
|
|
58
|
+
before the running mean, running variance, or batch counter changes. Evaluation
|
|
59
|
+
mode and disabled running statistics are covered as well.
|
|
60
|
+
|
|
61
|
+
### Unproven claims
|
|
62
|
+
|
|
63
|
+
The per-layer scalar check introduces a device-to-host synchronization on
|
|
64
|
+
accelerators; its training-throughput cost has not been benchmarked.
|