zpower 1.0.0__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.
- zpower-1.0.0/PKG-INFO +157 -0
- zpower-1.0.0/README.md +128 -0
- zpower-1.0.0/pyproject.toml +32 -0
- zpower-1.0.0/setup.cfg +4 -0
- zpower-1.0.0/tests/test_integration.py +121 -0
- zpower-1.0.0/tests/test_nipgraph.py +102 -0
- zpower-1.0.0/tests/test_otux.py +134 -0
- zpower-1.0.0/tests/test_safe_math.py +92 -0
- zpower-1.0.0/tests/test_stabilize.py +140 -0
- zpower-1.0.0/tests/test_weights.py +153 -0
- zpower-1.0.0/zpower/__init__.py +241 -0
- zpower-1.0.0/zpower/_trainer.py +113 -0
- zpower-1.0.0/zpower/compat/__init__.py +4 -0
- zpower-1.0.0/zpower/compat/torch_wrap.py +178 -0
- zpower-1.0.0/zpower/compat/transformers_wrap.py +38 -0
- zpower-1.0.0/zpower/compute/__init__.py +3 -0
- zpower-1.0.0/zpower/compute/safe_math.py +179 -0
- zpower-1.0.0/zpower/memory/__init__.py +3 -0
- zpower-1.0.0/zpower/memory/otux.py +322 -0
- zpower-1.0.0/zpower/monitor/__init__.py +3 -0
- zpower-1.0.0/zpower/monitor/nipgraph.py +176 -0
- zpower-1.0.0/zpower/stabilize/__init__.py +5 -0
- zpower-1.0.0/zpower/stabilize/grad_shield.py +151 -0
- zpower-1.0.0/zpower/stabilize/model_stabilizer.py +94 -0
- zpower-1.0.0/zpower/stabilize/stability_core.py +143 -0
- zpower-1.0.0/zpower/utils/__init__.py +5 -0
- zpower-1.0.0/zpower/utils/config.py +59 -0
- zpower-1.0.0/zpower/utils/health.py +16 -0
- zpower-1.0.0/zpower/utils/logging.py +24 -0
- zpower-1.0.0/zpower/weights/__init__.py +11 -0
- zpower-1.0.0/zpower/weights/fisher.py +98 -0
- zpower-1.0.0/zpower/weights/guard.py +143 -0
- zpower-1.0.0/zpower/weights/surgeon.py +204 -0
- zpower-1.0.0/zpower/weights/vault.py +234 -0
- zpower-1.0.0/zpower.egg-info/PKG-INFO +157 -0
- zpower-1.0.0/zpower.egg-info/SOURCES.txt +37 -0
- zpower-1.0.0/zpower.egg-info/dependency_links.txt +1 -0
- zpower-1.0.0/zpower.egg-info/requires.txt +24 -0
- zpower-1.0.0/zpower.egg-info/top_level.txt +1 -0
zpower-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zpower
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Intelligence layer for AI/ML — stabilization, selective memory, weight surgery
|
|
5
|
+
Author: NNN Bhoi
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/nnbhoi/zpower
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: numpy>=1.24
|
|
11
|
+
Provides-Extra: torch
|
|
12
|
+
Requires-Dist: torch>=2.0; extra == "torch"
|
|
13
|
+
Provides-Extra: hf
|
|
14
|
+
Requires-Dist: transformers>=4.35; extra == "hf"
|
|
15
|
+
Requires-Dist: torch>=2.0; extra == "hf"
|
|
16
|
+
Provides-Extra: audio
|
|
17
|
+
Requires-Dist: torch>=2.0; extra == "audio"
|
|
18
|
+
Requires-Dist: torchaudio>=2.0; extra == "audio"
|
|
19
|
+
Provides-Extra: full
|
|
20
|
+
Requires-Dist: torch>=2.0; extra == "full"
|
|
21
|
+
Requires-Dist: transformers>=4.35; extra == "full"
|
|
22
|
+
Requires-Dist: pandas>=2.0; extra == "full"
|
|
23
|
+
Requires-Dist: scikit-learn>=1.3; extra == "full"
|
|
24
|
+
Requires-Dist: torchaudio>=2.0; extra == "full"
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
27
|
+
Requires-Dist: black; extra == "dev"
|
|
28
|
+
Requires-Dist: mypy; extra == "dev"
|
|
29
|
+
|
|
30
|
+
# ZPower (zp) — v1
|
|
31
|
+
|
|
32
|
+
**Intelligence layer for AI/ML systems.**
|
|
33
|
+
Works *with* PyTorch and HuggingFace — not against them.
|
|
34
|
+
|
|
35
|
+
> Stabilize training. Protect good weights. Detect anomalies. Reduce wasted compute.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install zpower # numpy only (core features)
|
|
43
|
+
pip install zpower[torch] # + PyTorch support
|
|
44
|
+
pip install zpower[hf] # + HuggingFace Transformers
|
|
45
|
+
pip install zpower[full] # everything
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Quick Start
|
|
51
|
+
|
|
52
|
+
### Mode A — Pre-trained model (attach ZPower)
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
import zpower as zp
|
|
56
|
+
|
|
57
|
+
# Any PyTorch or HuggingFace model
|
|
58
|
+
zp_model = zp.attach(
|
|
59
|
+
my_model,
|
|
60
|
+
stabilize = True, # GradShield + StabilityCore
|
|
61
|
+
monitor = True, # NipGraph anomaly detection
|
|
62
|
+
weight_vault = True, # Record best weight checkpoints
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Forward pass — completely unchanged
|
|
66
|
+
output = zp_model(input_tensor)
|
|
67
|
+
|
|
68
|
+
# After loss + backward — ZPower hooks are active automatically
|
|
69
|
+
loss.backward()
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Mode B — Training from scratch
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
import zpower as zp
|
|
76
|
+
|
|
77
|
+
trainer = zp.Trainer(
|
|
78
|
+
my_model,
|
|
79
|
+
stabilize = True,
|
|
80
|
+
weight_vault = True,
|
|
81
|
+
weight_guard = False, # enable after first good run
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
trainer.fit(train_loader, epochs=20, lr=1e-3)
|
|
85
|
+
|
|
86
|
+
print(trainer.weight_report())
|
|
87
|
+
# { layers_vaulted: 12, total_snapshots: 47, overall_vault_score: 0.84 }
|
|
88
|
+
|
|
89
|
+
trainer.render_monitor()
|
|
90
|
+
# NipGraph ASCII dashboard
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Multi-model weight selection
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from zpower.weights import WeightSurgeon
|
|
97
|
+
|
|
98
|
+
surgeon = WeightSurgeon(conflict_resolution="highest_performer")
|
|
99
|
+
surgeon.add_source("run_jan.pt", label="jan", perf_score=0.72)
|
|
100
|
+
surgeon.add_source("run_mar.pt", label="mar", perf_score=0.89)
|
|
101
|
+
surgeon.add_source("run_may.pt", label="may", perf_score=0.81)
|
|
102
|
+
|
|
103
|
+
best_weights = surgeon.select_best()
|
|
104
|
+
new_model.load_state_dict(best_weights)
|
|
105
|
+
|
|
106
|
+
print(surgeon.selection_report())
|
|
107
|
+
# { 'transformer.layer.0.attn': 'mar', 'transformer.layer.1.ffn': 'may', ... }
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Module Overview
|
|
113
|
+
|
|
114
|
+
| Module | Purpose |
|
|
115
|
+
|---|---|
|
|
116
|
+
| `zp.memory.OtuxStore` | Selective context-aware memory — stores only important information |
|
|
117
|
+
| `zp.stabilize.GradShield` | Real-time gradient health: detects vanishing / exploding |
|
|
118
|
+
| `zp.stabilize.StabilityCore` | Loss EMA + plateau detection + LR signal |
|
|
119
|
+
| `zp.stabilize.ModelStabilizer` | Unified stabilization API |
|
|
120
|
+
| `zp.monitor.NipGraph` | Parity-aware training anomaly detection |
|
|
121
|
+
| `zp.weights.WeightVault` | Performance-gated weight snapshots |
|
|
122
|
+
| `zp.weights.WeightSurgeon` | Multi-model best weight selection (Fisher-guided) |
|
|
123
|
+
| `zp.weights.WeightGuard` | EWC-style catastrophic forgetting prevention |
|
|
124
|
+
| `zp.compute.safe_loss` | NaN-safe loss computation with rational fallback |
|
|
125
|
+
| `zp.compute.safe_divide` | Exact fraction arithmetic for recurring decimals |
|
|
126
|
+
| `zp.compat.ZPowerModel` | PyTorch model wrapper |
|
|
127
|
+
| `zp.compat.augment` | HuggingFace model augmentation |
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## How ZPower Reduces Load
|
|
132
|
+
|
|
133
|
+
| Problem | ZPower Solution | Effect |
|
|
134
|
+
|---|---|---|
|
|
135
|
+
| NaN crash mid-training | GradShield clips before NaN | No wasted restart |
|
|
136
|
+
| Plateau wastes epochs | StabilityCore signals LR reduction | Converges faster |
|
|
137
|
+
| Catastrophic forgetting | WeightGuard EWC penalty | Retains good knowledge |
|
|
138
|
+
| Starting training from scratch | WeightSurgeon merges best of past runs | Better initialization |
|
|
139
|
+
| Storing all context blindly | OTUX-S importance gate | 70%+ memory reduction |
|
|
140
|
+
| Anomaly found too late | NipGraph detects at first sign | Early intervention |
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Research Origins
|
|
145
|
+
|
|
146
|
+
All core systems are original research by **NNN Bhoi**:
|
|
147
|
+
|
|
148
|
+
- **OTUX-S** — from PERATHINK research paper (selective 3D coordinate memory)
|
|
149
|
+
- **NipGraph** — from NipGraph research paper (parity-aware state tracking)
|
|
150
|
+
- **GradShield + StabilityCore** — from MUSAI vGPU engine (M2 + M4)
|
|
151
|
+
- **WeightVault / Surgeon / Guard** — original ZPower research
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
MIT — free to use in any project.
|
zpower-1.0.0/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# ZPower (zp) — v1
|
|
2
|
+
|
|
3
|
+
**Intelligence layer for AI/ML systems.**
|
|
4
|
+
Works *with* PyTorch and HuggingFace — not against them.
|
|
5
|
+
|
|
6
|
+
> Stabilize training. Protect good weights. Detect anomalies. Reduce wasted compute.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install zpower # numpy only (core features)
|
|
14
|
+
pip install zpower[torch] # + PyTorch support
|
|
15
|
+
pip install zpower[hf] # + HuggingFace Transformers
|
|
16
|
+
pip install zpower[full] # everything
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
### Mode A — Pre-trained model (attach ZPower)
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import zpower as zp
|
|
27
|
+
|
|
28
|
+
# Any PyTorch or HuggingFace model
|
|
29
|
+
zp_model = zp.attach(
|
|
30
|
+
my_model,
|
|
31
|
+
stabilize = True, # GradShield + StabilityCore
|
|
32
|
+
monitor = True, # NipGraph anomaly detection
|
|
33
|
+
weight_vault = True, # Record best weight checkpoints
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Forward pass — completely unchanged
|
|
37
|
+
output = zp_model(input_tensor)
|
|
38
|
+
|
|
39
|
+
# After loss + backward — ZPower hooks are active automatically
|
|
40
|
+
loss.backward()
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Mode B — Training from scratch
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import zpower as zp
|
|
47
|
+
|
|
48
|
+
trainer = zp.Trainer(
|
|
49
|
+
my_model,
|
|
50
|
+
stabilize = True,
|
|
51
|
+
weight_vault = True,
|
|
52
|
+
weight_guard = False, # enable after first good run
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
trainer.fit(train_loader, epochs=20, lr=1e-3)
|
|
56
|
+
|
|
57
|
+
print(trainer.weight_report())
|
|
58
|
+
# { layers_vaulted: 12, total_snapshots: 47, overall_vault_score: 0.84 }
|
|
59
|
+
|
|
60
|
+
trainer.render_monitor()
|
|
61
|
+
# NipGraph ASCII dashboard
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Multi-model weight selection
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from zpower.weights import WeightSurgeon
|
|
68
|
+
|
|
69
|
+
surgeon = WeightSurgeon(conflict_resolution="highest_performer")
|
|
70
|
+
surgeon.add_source("run_jan.pt", label="jan", perf_score=0.72)
|
|
71
|
+
surgeon.add_source("run_mar.pt", label="mar", perf_score=0.89)
|
|
72
|
+
surgeon.add_source("run_may.pt", label="may", perf_score=0.81)
|
|
73
|
+
|
|
74
|
+
best_weights = surgeon.select_best()
|
|
75
|
+
new_model.load_state_dict(best_weights)
|
|
76
|
+
|
|
77
|
+
print(surgeon.selection_report())
|
|
78
|
+
# { 'transformer.layer.0.attn': 'mar', 'transformer.layer.1.ffn': 'may', ... }
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Module Overview
|
|
84
|
+
|
|
85
|
+
| Module | Purpose |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `zp.memory.OtuxStore` | Selective context-aware memory — stores only important information |
|
|
88
|
+
| `zp.stabilize.GradShield` | Real-time gradient health: detects vanishing / exploding |
|
|
89
|
+
| `zp.stabilize.StabilityCore` | Loss EMA + plateau detection + LR signal |
|
|
90
|
+
| `zp.stabilize.ModelStabilizer` | Unified stabilization API |
|
|
91
|
+
| `zp.monitor.NipGraph` | Parity-aware training anomaly detection |
|
|
92
|
+
| `zp.weights.WeightVault` | Performance-gated weight snapshots |
|
|
93
|
+
| `zp.weights.WeightSurgeon` | Multi-model best weight selection (Fisher-guided) |
|
|
94
|
+
| `zp.weights.WeightGuard` | EWC-style catastrophic forgetting prevention |
|
|
95
|
+
| `zp.compute.safe_loss` | NaN-safe loss computation with rational fallback |
|
|
96
|
+
| `zp.compute.safe_divide` | Exact fraction arithmetic for recurring decimals |
|
|
97
|
+
| `zp.compat.ZPowerModel` | PyTorch model wrapper |
|
|
98
|
+
| `zp.compat.augment` | HuggingFace model augmentation |
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## How ZPower Reduces Load
|
|
103
|
+
|
|
104
|
+
| Problem | ZPower Solution | Effect |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| NaN crash mid-training | GradShield clips before NaN | No wasted restart |
|
|
107
|
+
| Plateau wastes epochs | StabilityCore signals LR reduction | Converges faster |
|
|
108
|
+
| Catastrophic forgetting | WeightGuard EWC penalty | Retains good knowledge |
|
|
109
|
+
| Starting training from scratch | WeightSurgeon merges best of past runs | Better initialization |
|
|
110
|
+
| Storing all context blindly | OTUX-S importance gate | 70%+ memory reduction |
|
|
111
|
+
| Anomaly found too late | NipGraph detects at first sign | Early intervention |
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Research Origins
|
|
116
|
+
|
|
117
|
+
All core systems are original research by **NNN Bhoi**:
|
|
118
|
+
|
|
119
|
+
- **OTUX-S** — from PERATHINK research paper (selective 3D coordinate memory)
|
|
120
|
+
- **NipGraph** — from NipGraph research paper (parity-aware state tracking)
|
|
121
|
+
- **GradShield + StabilityCore** — from MUSAI vGPU engine (M2 + M4)
|
|
122
|
+
- **WeightVault / Surgeon / Guard** — original ZPower research
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
MIT — free to use in any project.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=42", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "zpower"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Intelligence layer for AI/ML — stabilization, selective memory, weight surgery"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
authors = [{ name = "NNN Bhoi" }]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
dependencies = ["numpy>=1.24"]
|
|
14
|
+
|
|
15
|
+
[project.optional-dependencies]
|
|
16
|
+
torch = ["torch>=2.0"]
|
|
17
|
+
hf = ["transformers>=4.35", "torch>=2.0"]
|
|
18
|
+
audio = ["torch>=2.0", "torchaudio>=2.0"]
|
|
19
|
+
full = ["torch>=2.0", "transformers>=4.35", "pandas>=2.0",
|
|
20
|
+
"scikit-learn>=1.3", "torchaudio>=2.0"]
|
|
21
|
+
dev = ["pytest>=7.0", "black", "mypy"]
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://github.com/nnbhoi/zpower"
|
|
25
|
+
|
|
26
|
+
[tool.setuptools.packages.find]
|
|
27
|
+
where = ["."]
|
|
28
|
+
include = ["zpower*"]
|
|
29
|
+
|
|
30
|
+
[tool.pytest.ini_options]
|
|
31
|
+
testpaths = ["tests"]
|
|
32
|
+
addopts = "-v --tb=short"
|
zpower-1.0.0/setup.cfg
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# tests/test_integration.py — Full pipeline, no torch required
|
|
2
|
+
import numpy as np
|
|
3
|
+
import pytest
|
|
4
|
+
import zpower as zp
|
|
5
|
+
from zpower.memory.otux import OtuxStore
|
|
6
|
+
from zpower.stabilize.model_stabilizer import ModelStabilizer
|
|
7
|
+
from zpower.monitor.nipgraph import NipGraph
|
|
8
|
+
from zpower.weights.vault import WeightVault
|
|
9
|
+
from zpower.weights.surgeon import WeightSurgeon
|
|
10
|
+
from zpower.compute.safe_math import safe_loss, safe_divide
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_import_and_version():
|
|
14
|
+
assert zp.__version__ == "1.0.0"
|
|
15
|
+
assert zp.__author__ == "NNN Bhoi"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_info_no_crash(capsys):
|
|
19
|
+
zp.info()
|
|
20
|
+
captured = capsys.readouterr()
|
|
21
|
+
assert "ZPower" in captured.out
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ── Full numpy pipeline ────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
def test_full_otux_pipeline():
|
|
27
|
+
store = OtuxStore(dim=16, mode="selective")
|
|
28
|
+
rng = np.random.default_rng(0)
|
|
29
|
+
|
|
30
|
+
# Write several entries
|
|
31
|
+
for i in range(20):
|
|
32
|
+
v = rng.standard_normal(16).astype(np.float32)
|
|
33
|
+
store.write(f"entry_{i}", v, x=i, y=i % 3, z=i % 5, reward=rng.uniform(0, 1))
|
|
34
|
+
|
|
35
|
+
stats = store.filter_stats()
|
|
36
|
+
assert stats["total_seen"] == 20
|
|
37
|
+
assert stats["currently_stored"] <= 20
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_full_stabilizer_pipeline():
|
|
41
|
+
ms = ModelStabilizer()
|
|
42
|
+
ng = NipGraph(variables=["loss", "grad_norm"])
|
|
43
|
+
ms._nipgraph = ng
|
|
44
|
+
ms.stability_core._nipgraph = ng
|
|
45
|
+
|
|
46
|
+
losses = [3.0, 2.5, 2.0, 1.8, 1.5, 1.3, 1.2, 1.1, 1.05, 1.02]
|
|
47
|
+
for L in losses:
|
|
48
|
+
info = ms.on_loss(L)
|
|
49
|
+
assert "state" in info
|
|
50
|
+
|
|
51
|
+
# Gradient shield
|
|
52
|
+
g_healthy = np.ones(4, dtype=np.float32) * 0.5
|
|
53
|
+
g_exploding = np.ones(4, dtype=np.float32) * 100.0
|
|
54
|
+
assert ms.grad_shield.check(g_healthy) == "healthy"
|
|
55
|
+
assert ms.grad_shield.check(g_exploding) == "exploding"
|
|
56
|
+
|
|
57
|
+
shielded = ms.grad_shield.shield(g_exploding)
|
|
58
|
+
assert float(np.linalg.norm(shielded)) <= 5.0 + 1e-4
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_vault_and_surgeon_pipeline():
|
|
62
|
+
rng = np.random.default_rng(42)
|
|
63
|
+
|
|
64
|
+
class FakeModel:
|
|
65
|
+
def __init__(self, seed):
|
|
66
|
+
_rng = np.random.default_rng(seed)
|
|
67
|
+
self.fc1 = _rng.standard_normal((8, 4)).astype(np.float32)
|
|
68
|
+
self.fc2 = _rng.standard_normal((4, 2)).astype(np.float32)
|
|
69
|
+
|
|
70
|
+
# Vault: record good model
|
|
71
|
+
vault = WeightVault(vault_threshold=0.0)
|
|
72
|
+
vault.record(FakeModel(0), {
|
|
73
|
+
"loss": 0.1, "loss_reference": 2.0,
|
|
74
|
+
"val_accuracy": 0.95, "confidence": 0.90,
|
|
75
|
+
"grad_health": "healthy", "curvature": "flat",
|
|
76
|
+
})
|
|
77
|
+
assert vault.summary()["total_snapshots"] > 0
|
|
78
|
+
|
|
79
|
+
# Surgeon: merge two models
|
|
80
|
+
surgeon = WeightSurgeon(conflict_resolution="highest_performer")
|
|
81
|
+
sd1 = {"fc1": FakeModel(0).fc1, "fc2": FakeModel(0).fc2}
|
|
82
|
+
sd2 = {"fc1": FakeModel(1).fc1, "fc2": FakeModel(1).fc2}
|
|
83
|
+
surgeon.add_source(sd1, label="model_a", perf_score=0.6)
|
|
84
|
+
surgeon.add_source(sd2, label="model_b", perf_score=0.9)
|
|
85
|
+
best = surgeon.select_best()
|
|
86
|
+
assert "fc1" in best
|
|
87
|
+
assert "fc2" in best
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_safe_math_pipeline():
|
|
91
|
+
# Normal loss
|
|
92
|
+
p = np.array([0.3, 0.3, 0.4])
|
|
93
|
+
t = np.array([0.0, 0.0, 1.0])
|
|
94
|
+
loss = safe_loss(p, t, "mse")
|
|
95
|
+
assert loss > 0
|
|
96
|
+
|
|
97
|
+
# Recurring division
|
|
98
|
+
token = safe_divide(1, 3)
|
|
99
|
+
assert abs(float(token) - 1/3) < 1e-9
|
|
100
|
+
|
|
101
|
+
# Deduplication
|
|
102
|
+
token2 = safe_divide(1, 3)
|
|
103
|
+
assert token.name == token2.name
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_nipgraph_training_simulation():
|
|
107
|
+
ng = NipGraph(variables=["loss", "accuracy"])
|
|
108
|
+
|
|
109
|
+
# Simulate healthy training
|
|
110
|
+
for i in range(30):
|
|
111
|
+
loss = 2.0 * np.exp(-0.1 * i) + 0.01 * np.random.default_rng(i).standard_normal()
|
|
112
|
+
acc = 1.0 - loss / 2.0
|
|
113
|
+
ng.update("loss", max(loss, 0.01))
|
|
114
|
+
ng.update("accuracy", min(max(acc, 0.0), 1.0))
|
|
115
|
+
|
|
116
|
+
state = ng.check()
|
|
117
|
+
assert "loss" in state
|
|
118
|
+
assert "accuracy" in state
|
|
119
|
+
# No alerts expected in healthy training
|
|
120
|
+
# (may or may not converge depending on values — just check no crash)
|
|
121
|
+
assert isinstance(state["loss"]["track"], str)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# tests/test_nipgraph.py
|
|
2
|
+
import pytest
|
|
3
|
+
from zpower.monitor.nipgraph import NipGraph
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_basic_update_no_alert():
|
|
7
|
+
ng = NipGraph(variables=["loss"], band_width=0.5)
|
|
8
|
+
for i in range(5):
|
|
9
|
+
ng.update("loss", 1.0 - i * 0.05)
|
|
10
|
+
state = ng.check()
|
|
11
|
+
assert "loss" in state
|
|
12
|
+
assert state["loss"]["alert"] is False
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_track_classification_positive_stable():
|
|
16
|
+
ng = NipGraph(variables=["loss"], band_width=0.5)
|
|
17
|
+
for _ in range(10):
|
|
18
|
+
ng.update("loss", 1.0)
|
|
19
|
+
state = ng.check()
|
|
20
|
+
assert state["loss"]["track"] in ("x_M", "x_W")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_alert_fires_on_bad_jump():
|
|
24
|
+
ng = NipGraph(variables=["loss"], band_width=0.05)
|
|
25
|
+
# Establish stable positive trend
|
|
26
|
+
for _ in range(10):
|
|
27
|
+
ng.update("loss", 1.0)
|
|
28
|
+
# Sudden negative spike (EMA positive, value hugely negative)
|
|
29
|
+
ng.update("loss", -100.0)
|
|
30
|
+
state = ng.check()
|
|
31
|
+
# Track should have jumped to Y_W territory
|
|
32
|
+
alerts = ng.alerts()
|
|
33
|
+
# Either direct alert or track is Y_W
|
|
34
|
+
assert state["loss"]["track"] in ("Y_M", "Y_W") or len(alerts) > 0
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_convergence_detection():
|
|
38
|
+
ng = NipGraph(variables=["loss"], band_width=0.5)
|
|
39
|
+
# Feed very stable values — should converge
|
|
40
|
+
for _ in range(25):
|
|
41
|
+
ng.update("loss", 0.001)
|
|
42
|
+
assert ng.is_converged() is True
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_not_converged_with_variance():
|
|
46
|
+
ng = NipGraph(variables=["loss"])
|
|
47
|
+
import random
|
|
48
|
+
rng = random.Random(42)
|
|
49
|
+
for _ in range(25):
|
|
50
|
+
ng.update("loss", rng.uniform(0.5, 5.0))
|
|
51
|
+
assert ng.is_converged() is False
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_multiple_variables():
|
|
55
|
+
ng = NipGraph(variables=["loss", "grad_norm", "accuracy"])
|
|
56
|
+
ng.update("loss", 2.0)
|
|
57
|
+
ng.update("grad_norm", 0.5)
|
|
58
|
+
ng.update("accuracy", 0.8)
|
|
59
|
+
state = ng.check()
|
|
60
|
+
assert set(state.keys()) == {"loss", "grad_norm", "accuracy"}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_auto_add_new_variable():
|
|
64
|
+
ng = NipGraph(variables=["loss"])
|
|
65
|
+
ng.update("new_metric", 1.0) # not in initial list
|
|
66
|
+
state = ng.check()
|
|
67
|
+
assert "new_metric" in state
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_clear_alerts():
|
|
71
|
+
ng = NipGraph(variables=["loss"], band_width=0.01)
|
|
72
|
+
for _ in range(5):
|
|
73
|
+
ng.update("loss", 1.0)
|
|
74
|
+
ng.update("loss", -999.0)
|
|
75
|
+
ng.clear_alerts()
|
|
76
|
+
assert ng.alerts() == []
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_locate_fault_no_anomaly():
|
|
80
|
+
ng = NipGraph(variables=["loss"])
|
|
81
|
+
ng.update("loss", 1.0)
|
|
82
|
+
msg = ng.locate_fault("loss")
|
|
83
|
+
assert "No anomaly" in msg
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_render_panels_no_crash(capsys):
|
|
87
|
+
ng = NipGraph(variables=["loss", "accuracy"])
|
|
88
|
+
for _ in range(3):
|
|
89
|
+
ng.update("loss", 1.0)
|
|
90
|
+
ng.update("accuracy", 0.9)
|
|
91
|
+
ng.render_panels()
|
|
92
|
+
captured = capsys.readouterr()
|
|
93
|
+
assert "NipGraph" in captured.out
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_status_keys():
|
|
97
|
+
ng = NipGraph(variables=["loss"])
|
|
98
|
+
s = ng.status()
|
|
99
|
+
assert "step" in s
|
|
100
|
+
assert "variables" in s
|
|
101
|
+
assert "converged" in s
|
|
102
|
+
assert "alerts" in s
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# tests/test_otux.py
|
|
2
|
+
import numpy as np
|
|
3
|
+
import pytest
|
|
4
|
+
from zpower.memory.otux import OtuxStore, ImportanceWeights
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def make_vec(dim=256, seed=None):
|
|
8
|
+
rng = np.random.default_rng(seed)
|
|
9
|
+
v = rng.standard_normal(dim).astype(np.float32)
|
|
10
|
+
return v / (np.linalg.norm(v) + 1e-10)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# ── ImportanceWeights ──────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
def test_weights_sum_to_one():
|
|
16
|
+
w = ImportanceWeights(0.35, 0.30, 0.20, 0.15)
|
|
17
|
+
assert abs(w.novelty + w.reward + w.context_fit + w.recurrence - 1.0) < 1e-5
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_weights_bad_sum():
|
|
21
|
+
with pytest.raises(ValueError):
|
|
22
|
+
ImportanceWeights(0.5, 0.5, 0.5, 0.5)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ── OtuxStore basic ────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
def test_store_and_len():
|
|
28
|
+
store = OtuxStore(dim=8, mode="full")
|
|
29
|
+
store.write("hello", make_vec(8, 0))
|
|
30
|
+
store.write("world", make_vec(8, 1))
|
|
31
|
+
assert len(store) == 2
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_query_returns_results():
|
|
35
|
+
store = OtuxStore(dim=8, mode="full")
|
|
36
|
+
v = make_vec(8, 42)
|
|
37
|
+
store.write("alpha", v)
|
|
38
|
+
results = store.query(v, top_k=1)
|
|
39
|
+
assert len(results) == 1
|
|
40
|
+
assert results[0]["token"] == "alpha"
|
|
41
|
+
assert results[0]["sim"] > 0.99
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_query_empty_store():
|
|
45
|
+
store = OtuxStore(dim=8)
|
|
46
|
+
results = store.query(make_vec(8), top_k=5)
|
|
47
|
+
assert results == []
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_coord_retrieval():
|
|
51
|
+
store = OtuxStore(dim=8, mode="full")
|
|
52
|
+
store.write("deep", make_vec(8, 0), x=0, y=0, z=3)
|
|
53
|
+
store.write("surface", make_vec(8, 1), x=0, y=0, z=0)
|
|
54
|
+
deep_results = store.query_by_coord(z=3, tol=0.1)
|
|
55
|
+
assert len(deep_results) == 1
|
|
56
|
+
assert deep_results[0].token == "deep"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_force_write_bypasses_gate():
|
|
60
|
+
store = OtuxStore(dim=8, mode="selective", importance_threshold=0.99)
|
|
61
|
+
result = store.write("forced", make_vec(8, 0), reward=0.0, force=True)
|
|
62
|
+
assert result == "forced"
|
|
63
|
+
assert len(store) == 1
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# ── Importance gate ────────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
def test_discard_low_reward():
|
|
69
|
+
store = OtuxStore(
|
|
70
|
+
dim=8,
|
|
71
|
+
mode="selective",
|
|
72
|
+
importance_threshold=0.65,
|
|
73
|
+
forget_threshold=0.30,
|
|
74
|
+
weights={"novelty": 0.35, "reward": 0.30, "context_fit": 0.20, "recurrence": 0.15},
|
|
75
|
+
)
|
|
76
|
+
# reward=0.0, no context → score will be low → discard
|
|
77
|
+
result = store.write("junk", make_vec(8, 99), reward=0.0)
|
|
78
|
+
# First entry: novelty=1.0, reward=0.0, context=0.5, recurrence=log(2)/5
|
|
79
|
+
# score = 0.35*1.0 + 0.30*0.0 + 0.20*0.5 + 0.15*~0.14 ≈ 0.47 → below 0.65
|
|
80
|
+
# Could be buffered or discarded — just not stored immediately
|
|
81
|
+
assert result in ("discarded", "buffered")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_high_reward_stored():
|
|
85
|
+
store = OtuxStore(dim=8, mode="selective", importance_threshold=0.65)
|
|
86
|
+
result = store.write("important", make_vec(8, 1), reward=1.0)
|
|
87
|
+
# novelty=1.0 (first entry), reward=1.0 → score ≈ 0.35+0.30+0.10+0.02 = 0.77 → stored
|
|
88
|
+
assert result == "stored"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_buffer_three_strikes():
|
|
92
|
+
store = OtuxStore(
|
|
93
|
+
dim=8, mode="selective",
|
|
94
|
+
importance_threshold=0.90, # high threshold → most go to buffer
|
|
95
|
+
forget_threshold=0.05,
|
|
96
|
+
buffer_strikes=3,
|
|
97
|
+
)
|
|
98
|
+
v = make_vec(8, 7)
|
|
99
|
+
for _ in range(3):
|
|
100
|
+
store.write("repeat", v, reward=0.4)
|
|
101
|
+
# After 3 strikes → promoted to store
|
|
102
|
+
assert len(store) >= 1
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# ── Filter stats ───────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
def test_filter_stats_keys():
|
|
108
|
+
store = OtuxStore(dim=8, mode="full")
|
|
109
|
+
store.write("a", make_vec(8, 0))
|
|
110
|
+
s = store.filter_stats()
|
|
111
|
+
assert "stored" in s
|
|
112
|
+
assert "discarded" in s
|
|
113
|
+
assert "compression_ratio" in s
|
|
114
|
+
assert "currently_stored" in s
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_clear():
|
|
118
|
+
store = OtuxStore(dim=8, mode="full")
|
|
119
|
+
store.write("x", make_vec(8))
|
|
120
|
+
store.clear()
|
|
121
|
+
assert len(store) == 0
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# ── Vector validation ──────────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
def test_wrong_dim_raises():
|
|
127
|
+
store = OtuxStore(dim=8)
|
|
128
|
+
with pytest.raises(ValueError):
|
|
129
|
+
store.write("bad", np.ones(16, dtype=np.float32))
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_repr():
|
|
133
|
+
store = OtuxStore(dim=8, mode="full")
|
|
134
|
+
assert "OtuxStore" in repr(store)
|